DIP是依赖倒置原则:一种软件架构设计的原则(抽象概念)。依赖于抽象不依赖于细节

IOC即为控制反转(Inversion of Control):传统开发,上端依赖(调用/指定)下端对象,会有依赖,把对下端对象的依赖转移到第三方容器(工厂+配置文件+反射),能够程序拥有更好的扩展性,是DIP的具体实现方式,可以用来减低计算机代码之间的耦合度。

DI 即为依赖注入(Dependency Injection):

  1. 是实现IOC的手段和方法,就是能做到构造某个对象时,将依赖的对象自动初始化并注入 :
  2. 有三种注入方式:构造函数注入--属性注入--方法注入(按时间顺序):
  3. 构造函数注入用的最多,默认找参数最多的构造函数,可以不用特性,可以去掉对容器的依赖

Unity容器:是微软推出的IOC框架,使用这个框架,可以实现AOP面向切面编程,便于代码的后期维护,此外,这套框架还自带单例模式,可以提高程序的运行效率。

上面介绍了一下Unity,IOC,DI的概念,那我们项目中什么时候会使用Unity呢,总结分析得到如下情况:

  1. 所构建的系统依赖于健全的面向对象原则,但是大量不同的代码交织在一起而难以维护。
  2. 构建的对象和类需要依赖其他对象或类。
  3. 依赖于复杂的或需要抽象的对象。
  4. 希望利用构造函数、方法或属性的调用注入优势。
  5. 希望管理对象实例的生命周期。
  6. 希望能够在运行时管理并改变依赖关系。
  7. 希望在拦截方法或属性调用的时候生成一个策略链或管道处理容器来实现横切(AOP)任务。
  8. 希望在Web Application中的回发操作时能够缓存或持久化依赖关系。

使用Unity的好处:

  • Unity支持简单对象创建,特别是分层对象结构和依赖,以简化程序代码。其包含一个编译那些可能存在依赖于其他对象的对象实例机制。
  • Unity支持必要的抽象,其允许开发者在运行时或配置去指定依赖关系同时可以简单的管理横切点(AOP)。
  • Unity增加了推迟到容器组件配置的灵活性。其同样支持一个容器层次的结构。
  • Unity拥有服务定位能力,对于一个程序在许多情况下重复使用组件来分离和集中功能是非常有用的。
  • Unity允许客户端储存或缓存容器。对于在ASP.NET Web applications中开发者将容器持久化于ASP.NET中的session或application中特别有效。
  • Unity拥有拦截能力,其允许开发者通过创建并执行handlers(在方法或属性被调用到达之前)来为已存在的组件增加一个函数,并再次为返回调用结果。
  • Unity可以从标准配置系统中读取配置信息,例如:XML文件,同时使用配置文件来配置容器。
  • Unity支持开发者实现自定义容器扩展,例如:你可以实现方法来允许额外的对象构造和容器特征,例如缓存。
  • Unity允许架构师和开发者在现代化的程序中更简单的实现通用设计模式
 上面我们分析了unity使用的场景以及unity能够解决的问题,那下面我们来具体讲解一下如何使用unity。首先我们先准备一下基础类:
1:实体类,程序集和命名空间都是:Entity
 namespace Entity
{
public class User
{
public string UserName { set; get; }
public int UserId { set; get; }
public string Password { get; set; }
}
}
namespace Entity
{
public class Role
{
public int Id { set; get; }
}
}
namespace Entity
{
public class Department
{
public int Id { set; get; }
public string Name { get; set; }
}
}
namespace Entity
{
public class Company
{
public string CompanyName { set; get; }
public int CompanyId { set; get; }
}
}

2:接口类,程序集和命名空间都是:IService

 namespace IService
{
public interface ICompany
{
void RegCompany(Company company);
}
} namespace IService
{
public interface IRepository<T>
{
int Insert(T t);
T LoadById(int id);
}
}
namespace IService
{
public interface IRole
{
int Insert(Role role);
}
} namespace IService
{
public interface IUser
{
void RegUser(User user);
}
}

3:业务类,程序集合是:Service;命名空间是:Service1

 namespace Service1
{
public class UserManager : IUser
{
public UserManager()
{
Console.WriteLine("userManager无参构造函数");
}
public UserManager(int id,string name)
{
Console.WriteLine($"userManager有参构造函数id={id};name={name}");
}
public UserManager(int id)
{
Console.WriteLine($"userManager有参构造函数id={id};");
}
public void RegUser(User user)
{
Console.WriteLine($"注册用户,UserName={user.UserName},UserId={user.UserId}");
}
} public class RoleManager : IRole
{
public RoleManager(int id)
{
Console.WriteLine($"有参构造函数:Id={id}");
}
public int Insert(Role role)
{
Console.WriteLine($"insert方法{role.Id}");
return ;
}
} public class DepartmentManager : IRepository<Department>
{
public int Insert(Department t)
{
Console.WriteLine($"新增 id={t.Id},Name={t.Name}");
return ;
} public Department LoadById(int id)
{
Console.WriteLine("获取Id");
return null;
}
} public class CompanyManager : ICompany
{
public CompanyManager()
{
Console.WriteLine("CompanyManager无参构造函数");
}
public CompanyManager(int id, string name)
{
Console.WriteLine($"CompanyManager有参构造函数id={id};name={name}");
}
public CompanyManager(int id)
{
Console.WriteLine($"CompanyManager有参构造函数id={id};");
}
public void RegCompany(Company company)
{
Console.WriteLine($"注册公司,CompanyName={company.CompanyName},CompanyId={company.CompanyId}");
}
}
}

上面是基础类,下面的业务代码中有用到 !

一:我们使用代码来创建对象,如果仅仅使用代码来创建,则只需要再nuget上面添加:Unity,则可以实现下面的代码:
 static void Main(string[] args)
{
try
{
#region 普通方法的注册
//声明unityContainer容器
IUnityContainer container = new UnityContainer(); container.RegisterType(typeof(ICompany), typeof(CompanyManager));
var compay = container.Resolve<ICompany>();
compay.RegCompany(new Company() { CompanyId = , CompanyName = "companyName" }); //把UserManager映射成IUser,下次Resolve<IUser>直接new UserManager
container.RegisterType<IUser, UserManager>(); //1:等价于IUser u = new UserManager(); 调用无参的构造函数
var userService4NoParam = container.Resolve<IUser>(); //2:等价于IUser u = new UserManager(1, "wss"); 调用两个参数的构造函数
var userService4TwoParam = container.Resolve<IUser>(
new ResolverOverride[] {
new ParameterOverride("id",),
new ParameterOverride("name","wss")
}); //3:等价于IUser u = new UserManager(1); 调用一个参数的构造函数
var userService4OneParam = container.Resolve<IUser>(
new ResolverOverride[] { new ParameterOverride("id", ) });
userService4OneParam.RegUser(new User() { UserId = , UserName = "wss" }); #endregion Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine($"错误:{ex.Message}");
}

使用Unity来管理对象与对象之间的关系可以分为以下几步:

A、创建一个UnityContainer对象
B、通过UnityContainer对象的RegisterType方法来注册对象与对象之间的关系
C、通过UnityContainer对象的Resolve方法来获取指定对象关联的对象
 
二:使用配置文件来进行映射对象
上面第一种方式面向接口实现有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等,但是接口一定是需要实现的,如果一个子类实现换成另一个子类实现,就需要在代码中改动,或者建立一个工厂来根据条件生成,还是存着着一定的耦合关系。
而读取XML文件则用来削减程序的耦合问题,它把耦合从代码中移出去,放到统一的XML文件中,通过一个容器在需要的时候把这个依赖关系形成,即把需要的接口实现注入到需要它的类中,当需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以,这样可以实现对象的热插拨(有点象USB接口)。
 
上面说那么多,具体实现方式如下:
首先我们先从nuget上面添加:Unity.Configuration,然后在新增文件夹CfgFiles新增一个unity.config文件,具体代码和注释如下:
 <configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration"/>
<!--type="命名空间,程序集即是dll或者exe的命名空间"-->
</configSections> <unity>
<!--***别名设置开始,下面三种设置方式都可以***-->
<!--管理生命周期类型,以及一些类型别名的设置,方便我们映射对象的编写,比如同一个类型注册多次,我们只要在typeAlias添加一个类型别名,这样我们再添加这个类型映射的时候只要写个别名就可以了。-->
<alias alias="MyDateTime" type="System.DateTime" />
<typeAliases>
<!--寿命管理器类型-->
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,Microsoft.Practices.Unity" />
<typeAlias alias="external" type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager, Microsoft.Practices.Unity" />
<!--用户定义的类型别名-->
<typeAlias alias="UserManager" type="Service1.UserManager,Service" />
</typeAliases>
<aliases>
<!--type="命名空间.类名,程序集"-->
<add alias="IUser" type="IService.IUser,IService"></add>
</aliases>
<!--***别名设置结束***--> <!--**想要加unity自带拦截器的第1步**-->
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension,Unity.Interception.Configuration"/>
<!--<sectionExtension type="命名空间.类,程序集即是dll或者exe的命名空间"/>-->
<containers>
<!--如果有多个container,每个container的name是不能重复的,否则读取的时候会报错-->
<!--container为容器管理,下面包含多个类型映射,我们平常使用的构造器注册、属性注册和方法注册,就可以在constructor、property、method节点进行配置。-->
<container name="aopContainer">
<!--**想要加unity自带拦截器的第2步**-->
<extension type="Interception"/>
<!--容器中类型映射有3种,一种使用register,一种是instances,另外一种是types-->
<!--使用上面的别名Aliases-->
<register type="IUser" mapTo="UserManager">
<!--**想要加unity自带拦截器的第3步**-->
<interceptor type="InterfaceInterceptor"/>
<!--自己定义的拦截器开始-->
<interceptionBehavior type="MyTest.UnityWay.MonitorBehavior, MyTest"/>
<interceptionBehavior type="MyTest.UnityWay.LogBeforeBehavior, MyTest"/>
<interceptionBehavior type="MyTest.UnityWay.ParameterCheckBehavior, MyTest"/>
<interceptionBehavior type="MyTest.UnityWay.CachingBehavior, MyTest"/>
<interceptionBehavior type="MyTest.UnityWay.ExceptionLoggingBehavior, MyTest"/>
<interceptionBehavior type="MyTest.UnityWay.LogAfterBehavior, MyTest"/>
<!--自己定义的拦截器结束-->
</register> <!--注册类+构造函数-->
<!--<register type="命名空间.接口类型2,程序集" mapTo="命名空间.实现类型2,程序集" />-->
<register type="IService.IRole,IService" mapTo="Service1.RoleManager,Service"/> <instances>
<!--instances:这些对象被用容器的 RegisterInstance 方法进行注册,一般用于简单类型,并且是已知值,所以value是必填,type不写默认为sytem.string-->
<add name="MyInstance1" type="System.String" value="Some value" />
<add name="MyInstance2" type="MyDateTime" value="2019-02-19T11:13:00" />
</instances>
<types>
<!--type这些配置被container.RegisterType<TFrom,TTo>()注册-->
<type type="IService.ICompany,IService" mapTo="Service1.CompanyManager,Service" />
<!--<type name="注册名称可选" type="源类型(格式:命名空间.类名,程序集dll的名称),必填" mapTo="目标类型,可选" lifetime="声明周期,可选">-->
<!--lifetime:Transient:是默认的,每次创建一个实例;Singleton:单例模式,每次创建都返回同一个实例--> <!--[]方括号表示的是泛型参数,具体可以通过 typeof(IRepository<Department>).AssemblyQualifiedName,然后去掉Version=1.0.0.0, Culture=neutral, PublicKeyToken=null-->
<type type="IService.IRepository`1[[Entity.Department, Entity]],IService" mapTo="Service1.DepartmentManager,Service" />
</types>
</container>
</containers>
</unity>
</configuration>

然后下图是关于unity.config中节点的标签,可以参考一下:

然后我们程序中读取xml配置文件并且实现如下:

  public static void Show()
{
//配置UnityContainer
IUnityContainer container = GetContainer("aopContainer");//GetContainer(); //1:获取instances注册的类型,一般适用于简单的类型且值是固定的常量
var data4Str = container.Resolve<string>("MyInstance1");
var data4DateTime = container.Resolve<DateTime>("MyInstance2"); //2:获取types注册的类型
ICompany companyManager = container.Resolve<ICompany>();
companyManager.RegCompany(new Company() { CompanyId = , CompanyName = "公司" }); //3:获取register注册的类型
IUser userManager = container.Resolve<IUser>();
userManager.RegUser(new User() { UserName = "wss", UserId = }); //4:获取泛型类
IRepository<Department> department = container.Resolve<DepartmentManager>();
department.Insert(new Department() { Id = , Name = "部门1" }); //5:获取有参的构造函数
IRole roleManager = container.Resolve<RoleManager>(new ResolverOverride[] { new ParameterOverride("id", ) });
roleManager.Insert(new Role() { Id = });
} private static IUnityContainer GetContainer(string containerName = "")
{
//配置UnityContainer
IUnityContainer container = new UnityContainer();
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
//得到配置文件的路径
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config"); //从config文件中读取配置信息
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); //获取指定名称的配置节,UnityConfigurationSection.SectionName=unity,得到containers集合下面的container节点
UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); //unity.config中的containers下面可以有多个container,但是每个container的name一定要不同,不然会报错
if (string.IsNullOrEmpty(containerName))
{
//获取Unity.Config中的container没有设置Name属性的节点
configSection.Configure(container);
}
else
{
//获取节点aopContainer的container节点
configSection.Configure(container, containerName);
}
return container;
}

这样即实现了unity动态创建对象的功能,如果以后想要新增或者修改,只需要修改xml文件的映射即可!

三:unity的统一拦截器

使用拦截器之前,我们必须在nuget上面拉一下:Unity.Interception 和Unity.Interception.Configuration 这个两个dll,然后新增使用拦截器的代码,具体如下:

 using Entity;
using System;
using System.Collections.Generic;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
/// <summary>
/// 不需要特性
/// </summary>
public class CachingBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine("CachingBehavior");
//input.Target.GetType().GetCustomAttributes()
if (input.MethodBase.Name.Equals("GetUser"))
return input.CreateMethodReturn(new User() { UserId = , UserName = "wss" });
return getNext().Invoke(input, getNext);
} public bool WillExecute
{
get { return true; }
}
}
}
 //using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Collections.Generic;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
public class ExceptionLoggingBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine("ExceptionLoggingBehavior");
IMethodReturn methodReturn = getNext()(input, getNext);
if (methodReturn.Exception == null)
{
Console.WriteLine("无异常");
}
else
{
Console.WriteLine($"异常:{methodReturn.Exception.Message}");
}
return methodReturn;
} public bool WillExecute
{
get { return true; }
}
}
}
 //using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Collections.Generic;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
public class LogAfterBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine("LogAfterBehavior");
foreach (var item in input.Inputs)
{
Console.WriteLine(item.ToString());//反射获取更多信息
}
IMethodReturn methodReturn = getNext()(input, getNext);
Console.WriteLine("LogAfterBehavior" + methodReturn.ReturnValue);
return methodReturn;
} public bool WillExecute
{
get { return true; }
}
}
}
 //using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Collections.Generic;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
/// <summary>
/// 不需要特性
/// </summary>
public class LogBeforeBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine("LogBeforeBehavior");
foreach (var item in input.Inputs)
{
Console.WriteLine(item.ToString());//反射获取更多信息
}
return getNext().Invoke(input, getNext);
} public bool WillExecute
{
get { return true; }
}
}
}
 //using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
/// <summary>
/// 性能监控的AOP扩展
/// </summary>
public class MonitorBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine(this.GetType().Name);
string methodName = input.MethodBase.Name;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start(); var methodReturn = getNext().Invoke(input, getNext);//后续逻辑执行 stopwatch.Stop();
Console.WriteLine($"{this.GetType().Name}统计方法{methodName}执行耗时{stopwatch.ElapsedMilliseconds}ms"); return methodReturn;
} public bool WillExecute
{
get { return true; }
}
}
}
 //using Microsoft.Practices.Unity.InterceptionExtension;
using Entity;
using System;
using System.Collections.Generic;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.PolicyInjection.Pipeline; namespace MyTest.UnityWay
{
public class ParameterCheckBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Console.WriteLine("ParameterCheckBehavior");
User user = input.Inputs[] as User;
if (user.Password.Length < )
{
return input.CreateExceptionMethodReturn(new Exception("密码长度不能小于10位"));
}
else
{
Console.WriteLine("参数检测无误");
return getNext().Invoke(input, getNext);
}
} public bool WillExecute
{
get { return true; }
}
}
}
最后在unity.config中配置如三步:
 
这样就实现了在UserManager中增加了过滤器,执行UserManager方法之前都会先按顺序执行MonitorBehavior,LogBeforeBehavior,ParameterCheckBehavior,CachingBehavior,ExceptionLoggingBehavior,LogAfterBehavior 这些类的。以后想要修改或者增加拦截器则需要在这边配置即可!
 
通过上面我们总结了使用unity的几个dll文件,主dll只需要Unity,Unity.Configuration(读取配置unity.config需要),Unity.Interception和Unity.Interception.Configuration 这两个是拦截器所需要的!
 
 
 

c#中的Unity容器的更多相关文章

  1. 【中英对照】【EntLib6】【Unity】实验1:使用一个Unity容器

    Lab 1: Using a Unity Container 实验1:使用一个Unity容器 Estimated time to complete this lab: 15 minutes 估计完成时 ...

  2. Unity容器在asp.net mvc中的IOC应用及AOP应用

    <asp.net-mvc框架揭秘>一书中,有个示例,是使用unity容器来注入自定义的控制器工厂.代码示例可以自己去下载源码,在这里我就不说了.IOC容器的本质是解耦的实例化接口类,而如何 ...

  3. 【转载】C#中可使用Unity容器实现属性注入

    简介 Unity :Unity是微软团队开发的一个轻量级,可扩展的依赖注入容器,为松散耦合应用程序提供了很好的解决方案,支持构造器注入,属性注入,方法注入. 控制反转:(Inversion of Co ...

  4. Unity容器中AOP应用示例程序

    转发请注明出处:https://www.cnblogs.com/zhiyong-ITNote/p/9127001.html 实在没有找到Unity容器的AOP应用程序示例的说明,在微软官网找到了教程( ...

  5. MVC中使用Unity Ioc Container

    ASP.NET MVC中使用Unity Ioc Container   写在前面 安装Unity 添加服务层 IArticleRepository类型映射 服务注入到控制器 Global.asax初始 ...

  6. Unity容器的简单AOP与DI的应用Demo(基于asp.net mvc框架)

    转发请注明出处:https://home.cnblogs.com/u/zhiyong-ITNote/ 整个Demo是基于Controller-Service-Repository架构设计的,每一层之间 ...

  7. 在 mvc 4 中使用 unity 进行依赖注入

    在 mvc 4 中使用 unity 进行依赖注入 关于依赖注入的概念,这里就不再叙述了,我们用 mvc 4 结合 unity,使用构造函数来进行依 赖注入.具体步骤如下: 1. 首先建立 一个 mvc ...

  8. .NET中使用unity实现aop

    Unity是一款知名的依赖注入容器,其支持通过自定义扩展来扩充功能.在Unity软件包内默认包含了一个对象拦截(Interception)扩展定义.本篇文章将介绍如何使用对象拦截功能来帮助你分离横切关 ...

  9. Unity容器实现自动注册

    如何创建Unity容器? 首先NuGet搜索Unity, 该示例中使用的版本为4.0.1 新建控制台程序 示例中使用常规操作, 创建一个IPay接口, 分别有两个实现类: ApplePay.Huawe ...

随机推荐

  1. angular.uppercase()

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. vs2010 sp1 安装Silverlight5 语言版本不匹配的问题

    好久之前用silverlight写了个程序,今天心血来潮想给朋友看一下,朋友更新了sl5,但是运行不起来. 所以有点郁闷,于是打算更新项目到silverlight5. 装sp1后,下载silverli ...

  3. Windows下安装Kafka

    一.安装JDK 二.安装zooeleeper 下载安装包:http://zookeeper.apache.org/releases.html#download 下载后解压到一个目录: 1.进入Zook ...

  4. C# 异步编程之 Task 的使用

    (说明:随笔内容为学习task的笔记,资料来源:https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?redi ...

  5. 使用Ant Design的select组件时placeholder不生效/不起作用的解决办法

    先来说说使用Ant Design和Element-ui的感觉吧. 公司的项目开发中用的是vue+element-ui,使用了一通下来后,觉得element-ui虽然也有一些问题或坑,但这些小问题或坑凭 ...

  6. Java GC相关知识

    Java堆的分类 分为两类:YoungGen和OldGen.其中,YoungGen分为三部分:eden,from survivor和to survivor,比例默认是:8:1:1 PermGen不属于 ...

  7. Katalon Studio之接口测试中token处理

    前言 最近抽时间接触了一下Katalon Studio(后面简称KS),并且利用KS做了一些接口测试的试验,感觉还不错,不过其中接口授权中缺少通过token动态验证的方案,虽然KS支持Authoriz ...

  8. 分享使用tcb-router路由开发的云函数短信平台SDK

    上篇文章我们分享了如何使用纯的云函数开发的榛子短信短信(http://smsow.zhenzikj.com)SDK,由于微信对于未付费云函数个数的限制,这种方法存在缺陷,经过改进,使用tcb-rout ...

  9. ueditor编辑器多图上传为什么顺序打乱了

    我上一个版本用的是ueditor1.3.6,自从1.4.2版以后,“前端上传模块统一改用webuploader”,ueditor在多图上传一直考虑漏掉了图片顺序的问题. 我的网站在用户上传图片文章的时 ...

  10. Android 混淆那些事儿

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/WmJyiA3fDNriw5qXuoA9MA 作者:l ...