string.Format 异常 输入字符串的格式不正确_zhl71199713的专栏-CSDN博客_输入字符串的格式不正确怎么解决

mikel阅读(716)

来源: string.Format 异常 输入字符串的格式不正确_zhl71199713的专栏-CSDN博客_输入字符串的格式不正确怎么解决

最近做一个项目,在使用string.Format对数据进行整理的时候,一直报错:输入字符串的格式不正确。

纠结了很久。最后终于查了下资料终于知道了问题所在。现在将这个问题记录一下:

 

在代码中,需要将结果以Json格式,输出。最开始报错,写的代码如下:

public void TEST()
{
try
{
string t = “Jim”;
string sresult = string.Format(“{name:\”{0}\”}”, t);//这里报错
Console.Write(sresult);
}
catch (Exception ex)
{
}
}

后来查找了一些资料之后,才知道问题所在。
C# 中使用类似 {0}, {1:yyyy-MM-dd} 这样的格式占位符,如果被格式化字符串本身包含 { 或者 } 怎么办呢?答案是:用两个 { 或者 }连写表示单个。

我的代码问题就出在这个地方。修改之后,就好了,修改后的代码如下:

public void TEST()
{
try
{
string t = “Jim”;
string sresult = string.Format(“{{name:\”{0}\”}}”, t);
Console.Write(sresult);
}
catch (Exception ex)
{
}
}
事实上,很多情况下带特殊含义的字符都是这样转义的:如,

C# 中,当字符串常量带 @ 前导的时候,用两个 ” 连写表示一个 ” (半角双引号)

public void TEST()
{
try
{
string sresult = @”this is “”Jim”””;
string tresult=@”this is ‘Jim'”;
string tresult = @”this is “Jim””;//错误
Console.Write(sresult);
}
catch (Exception ex)
{
}
}
SQL 字符串常量,用两个 ‘ 连写表示一个 ‘ (半角单引号)
DECLARE @str6 varchar(100)
SET @str6 = ‘My UserName is ”Jinglecat”.’
PRINT @str6 — My UserName is ‘Jinglecat’.
正则表达式中用,两个 $ 连写表示一个 $ (dollar)
————————————————
版权声明:本文为CSDN博主「zhl71199713」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhl71199713/article/details/19846571

json格式的字符串使用string.Format()方法报错:输入字符串的格式不正确 - 梨花驿路 - 博客园

mikel阅读(1148)

来源: json格式的字符串使用string.Format()方法报错:输入字符串的格式不正确 – 梨花驿路 – 博客园

解决:把大括号转义一下就可以了啊,大括号的转义是两个{{  结尾是}}

 

 

今天看同事写的代码,发现他在使用string.format拼接类似json格式的数据时,大括号多了一对,感觉不对就查了查msdn,如下:

转义大括号

左大括号和右大括号被解释为格式项的开始和结束。因此,必须使用转义序列显示文本左大括号或右大括号。在固定文本中指定两个左大括号 (“{{“) 以显示一个左大括号 (“{“),或指定两个右大括号 (“}}”) 以显示一个右大括号 (“}”)。按照在格式项中遇到大括号的顺序依次解释它们。不支持解释嵌套的大括号。

解释转义大括号的方式会导致意外的结果。例如,考虑要显示一个左大括号、一个格式化为十进制数的数值和一个右大括号的格式项“{{{0:D}}}”。但是,实际是按照以下方式解释该格式项:

前两个左大括号 (“{{“) 被转义,生成一个左大括号。

之后的三个字符 (“{0:”) 被解释为格式项的开始。

下一个字符 (“D”) 将被解释为 Decimal 标准数值格式说明符,但后面的两个转义大括号 (“}}”) 生成单个大括号。由于得到的字符串 (“D}”) 不是标准数值格式说明符号,所以得到的字符串会被解释为用于显示字符串“D}”的自定义格式字符串。

最后一个大括号 (“}”) 被解释为格式项的结束。

显示的最终结果是字符串“{D}”。不会显示本来要格式化的数值。

在编写代码时,避免错误解释转义大括号和格式项的一种方法是单独格式化大括号和格式项。也就是说,在第一个格式化操作中显示文本左大括号,在下一操作中显示格式项的结果,然后在最后一个操作中显示文本右大括号。

设计模式如何提升 vivo 营销自动化业务扩展性 | 引擎篇01 - vivo互联网技术 - 博客园

mikel阅读(787)

来源: 设计模式如何提升 vivo 营销自动化业务扩展性 | 引擎篇01 – vivo互联网技术 – 博客园

在《vivo 营销自动化技术解密 |开篇》中,我们从整体上介绍了vivo营销自动化平台的业务架构、核心业务模块功能、系统架构和几大核心技术设计。

本次带来的是系列文章的第2篇,本文详细解析设计模式和相关应用如何帮助营销自动化业务提升系统扩展性,以及实践过程中的思考和总结。

一、引言

营销业务本身极具复杂多变性,特别是伴随着数字化营销蓬勃发展的趋势,在市场的不同时期、公司发展的不同阶段、面向不同的用户群体以及持续效果波动迭代,都会产生不同的营销策略决策。

当面对随时变化的业务场景时,系统的扩展性就显得非常重要。而在谈到系统设计扩展性的时候,总是首先会想到设计原则和设计模式。但设计模式不是银弹,并不能解决所有问题,它只是前人提炼总结出来的招式方法,需要开发者根据实际业务场景进行合理的选择、合适的变通,才能真正去解决实际场景中的问题,并总结形成自己的方法论。

那么接下来我们看看设计模式是如何帮助我们在营销策略引擎中提升系统扩展性的。

二、营销策略引擎

先简单介绍一下营销策略引擎:策略引擎是通过搭建可视化流程组件,定义各个流程节点,自动化执行活动业务流程,从而提供不同运营活动能力。其中核心活动业务流程主要包括三大部分:运营活动配置->运营活动审批->运营活动执行

  • 运营活动配置:运营人员在系统后台配置运营活动。包括活动名称、活动时间、触发条件、活动用户和具体推送渠道(如短信、微信、push推送等)。
  • 运营活动审批:品质/主管人员审批运营活动配置。审批流程涉及了活动审批节点和人员的配置,审批相关的回调操作配置。
  • 运营活动执行:系统自动化执行运营活动的过程。即具体的渠道如短信、微信、push等推送活动的任务执行下发流程,包括用户数据准备,数据下发推送和数据效果回收等。

三、设计模式具体应用

3.1 运营活动配置

3.1.1 工厂模式

具体场景

一般情况下,根据不同的用户和活动场景,运营借助数据分析会决策出不同的活动策略,比如需要创建短信推送策略、微信图文推送策略、App Push推送策略等。此时我们可以使用工厂模式,统一管理具体推送策略的创建。

模式分析

在GoF《设计模式:可复用面向对象软件的基础》中:工厂模式被分成了工厂方法和抽象工厂两类,而简单工厂模式(又称静态工厂模式)被看作是工厂方法的一种特例。不过由于简单工厂和工厂方法相对更简单和易于理解,代码可读性也更强,因此在实际项目中更加常用。

其中简单工厂的适用场景:

  • a.工厂类负责创建的对象比较少,工厂方法中的创建逻辑简单。
  • b.客户端无须关心创建具体对象的细节,仅需知道传入工厂类的类型参数。

而工厂方法的适用场景:

  • a.工厂类对象创建逻辑相对复杂,需要将工厂实例化延迟到其具体工厂子类中。
  • b.适合需求变更频繁的场景,可以利用不同的工厂实现类支持新的工厂创建方案,更符合开闭原则,扩展性更好。

典型代码示例

//抽象产品类
public abstract class Product {
    public abstract void method();
}
//具体的产品类 
class ProductA extends Product {
    @Override
    public void method() {
        //具体的执行逻辑
     }
}
//抽象工厂模板类
abstract class Factory<T> {
    abstract Product createProduct(Class<T> c);
}
//具体工厂实现类
class FactoryA extends Factory{
    @Override
    Product createProduct(Class c) {
        Product product = (Product) Class.forName(c.getName()).newInstance();
        return product;
    }
}

实际代码

/**
 * @author chenwangrong
 * 活动策略工厂类
 */
@Component
@Slf4j
public class ActivityStrategyFactory {
 
    /**
     * 获得渠道类型对应的策略
     *
     * @param channelType channelType
     * @return OperationServiceStrategy
     */
    public static ActivityStrategy getActivityStrategy(ChannelTypeEnum channelType) {
 
        ChannelTypeStrategyEnum channelTypeStrategyEnum = ChannelTypeStrategyEnum.getByChannelType(channelType);
        Assert.notNull(channelTypeStrategyEnum , "指定的渠道类型[channelType=" + channelType + "]不存在");
 
        String strategyName= channelTypeStrategyEnum.getHandlerName();
        Assert.notNull(strategyName, "指定的渠道类型[channelType=" + channelType + "未配置策略");
 
        return (ActivityStrategy)SpringContextHolder.getBean(handlerName);
    }
 
 
    public enum ChannelTypeStrategyEnum {
        /**
         * 短信渠道
         */
        SMS(ChannelTypeEnum.SMS, "smsActivityStrategy"),
        /**
         * 微信渠道
         */
        WX_NEWS(ChannelTypeEnum.WX, "wxActivityStrategy"),
        /**
         * push渠道
         */
        PUSH(ChannelTypeEnum.PUSH, "pushActivityStrategy"),;
 
        private final ChannelTypeEnum channelTypeEnum;
 
        private final String strategyName;
 
        ChannelTypeStrategyEnum (ChannelTypeEnum channelTypeEnum, String strategyName) {
            this.channelTypeEnum = channelTypeEnum;
            this.strategyName= strategyName;
        }
 
 
        public String getStrategyName() {
            return strategyName;
        }
 
        public static ChannelTypeStrategyEnum getByChannelType(ChannelTypeEnum channelTypeEnum) {
            for (ChannelTypeStrategyEnum channelTypeStrategyEnum : values()) {
                if (channelTypeEnum == channelTypeStrategyEnum.channelTypeEnum) {
                    return channelTypeStrategyEnum ;
                }
            }
            return null;
        }
    }
}

实践总结

在实际项目代码中我们采用的是简单工厂模式(静态工厂模式),实现时利用枚举(或者映射配置表)来保存渠道类型与具体策略实现类的映射关系,再结合Spring的单例模式,来进行策略类的创建。

相比于工厂方法模式,在满足业务的前提下,减少了工厂类数量,代码更加简单适用。

3.1.2 模板方法模式

具体场景

在创建不同类型运营活动策略的时候,可以发现除了保存具体活动渠道配置信息不一样之外,创建过程中很多操作流程是相同的:比如保存活动基本配置信息,审计日志上报,创建活动审批工单,创建完成后消息提醒等

原有实践

/**
 * 短信活动类
 *
 */
@Service
public class SmsActivityStrategy{
  
    /**
     * 执行渠道发送
     *
     * @param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         //保存活动基础信息
         saveActBaseConfig(param);
         //保存短信活动配置
         createSmsActivity(param);
         //审计日志上报 ...
         //创建活动审批工单 ...
         //消息通知 ...
         sendNotification(param);
    }
}
 
/**
 * Push活动类
 *
 */
@Service
public class PushActivityStrategy{
  
    /**
     * 执行渠道发送
     *
     * @param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         //保存活动基础信息
         saveActBaseConfig(param);
         //保存Push活动配置
         createChannelActivity(param);
         //审计日志上报 ...
         //创建活动审批工单 ...
         //消息通知 ...
         sendNotification(param);
    }
}
 
...

对于每种活动策略而言,这些操作都是必需的且操作流程都是固定的,所以可以将这些操作提取成公用的流程,此时就考虑到了模板方法模式。

模式分析

在GoF《设计模式:可复用面向对象软件的基础》:模板方法模式是在一个方法中定义一个算法骨架,并将某些步骤推迟到其子类中实现。模板方法模式允许子类在不改变算法结构的情况下重新定义算法的某些步骤。

上面所指的“算法”,可以理解为业务逻辑,而‘’算法骨架“即是模板,包含‘’算法骨架“的方法就是模板方法,这也是模板方法模式名称的来源。

模板方法模式适用场景:业务逻辑由确定的步骤组成,这些步骤的顺序要是固定不变的,不同的具体业务之间某些方法或者实现可以有所不同。

实现时一般通过抽象类来定义一个逻辑模板和框架,然后将无法确定的部分抽象成抽象方法交由子类来实现,调用逻辑仍在抽象类中完成。

典型代码示例

//模板类
public abstract class AbstractTemplate {
 
//业务逻辑1
protected abstract void doStep1();
//业务逻辑2
protected abstract void doStep2();
 
//模板方法
public void templateMethod(){
     this.doStep1();
     //公共逻辑
       ......
     this.doStep2();
   }
}
 
//具体实现类1
public class ConcreteClass1  extends AbstractTemplate {
  //实现业务逻辑1
  protected void doStep1()
  {
     //业务逻辑处理
  }
 
  //实现业务逻辑2
  protected void doStep2()
  {
    //业务逻辑处理
   }
}
 
//具体实现类2
public class ConcreteClass2  extends AbstractTemplate {
  //实现业务逻辑1
  protected void doStep1()
  {
     //业务逻辑处理
  }
 
  //实现业务逻辑2
  protected void doStep2()
  {
    //业务逻辑处理
   }
}
 
// 调用类
public class Client {
 public static void main(String[] args)
  {
    AbstractTemplate class1=new ConcreteClass1();
    AbstractTemplate class2=new ConcreteClass2();
   //调用模板方法
    class1.templateMethod();
    class2.templateMethod();
   }
}

实际代码

/**
 * 活动创建模板类
 *
 * @author chenwangrong
 */
@Slf4j
public abstract class AbstractActivityTemplate{
 
    /**
     * 保存具体活动配置
     *
     * @param param 活动参数
     * @return ProcessResult 处理结果
     */
    protected abstract ProcessResult createChannelActivity(ActParam param);
 
    /**
     * 执行活动创建
     *
     * @param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         //保存活动基础信息
         saveActBaseConfig(param);
         //保存具体渠道配置
         createChannelActivity(param);
         //审计日志上报 ...
         //消息通知 ...
    }
}
 
/**
 * 短信活动类
 *
 */
@Service
public class SmsActivityStrategy extends AbstractActivityTemplate{
  
    /**
     * 创建短信渠道活动配置
     *
     * @param msgParam msgParam
     */
    public ProcessResult createChannelActivity(ActParam param) {
         //仅需要实现:保存短信活动配置
         createSmsActivity(param);    
    }
}
 
(其他渠道活动类似,此处省略)
 
 
// 调用类
public class Client {
 public static void main(String[] args)
  {
    AbstractActivityTemplate smsActivityStrategy=new SmsActivityStrategy();
    AbstractActivityTemplate pushActivityStrategy=new PushActivityStrategy();
 
    ActParam param = new ActParam();
 
   //调用具体活动实现类
    smsActivityStrategy.createActivity(param);
    pushActivityStrategy.createActivity(param);
   }
}

实践总结

模板方法模式有两大作用:复用和扩展。复用是指所有的子类可以复用父类中提供的模板方法的代码。扩展是指框架通过模板模式提供功能扩展点,让用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。

模板方法非常适用于有通用业务逻辑处理流程,同时又在具体流程上存在一定差异的场景,可以通过将流程骨架抽取到模板类中,将可变的差异点设置为抽象方法,达到封装不变部分,扩展可变部分的目的。

3.1.3 策略模式

具体场景

上述我们通过模板方法模式抽取出了公共流程骨架,但这里还存在一个问题:调用类仍需要明确知道具体实现类是哪个,实例化后才可进行调用。也就是每一次增加新的渠道活动时,调用方都必须修改调用逻辑,添加新的活动实现类的初始化调用,显然不利用业务的扩展性。

在创建运营活动过程中,不同类型的活动会对应着不同的创建流程,调用方只需要根据渠道类型来进行区分,而无需理会其中具体的业务逻辑。此时策略模式是一个比较好的选择。

模式分析

在GoF《设计模式:可复用面向对象软件的基础》中:策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的调用方。

典型代码示例

//策略接口定义
public interface Strategy {
    void doStrategy();
}
​
//策略具体实现类(多个)
public class StrategyA implements Strategy{
    @Override
    public void doStrategy() {
    }
}
​
//上下文操作类, 屏蔽高层模块对策略的直接访问
public class Context {
    private Strategy strategy = null;
​
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
 
    public void doStrategy() {
        strategy.doStrategy();
    }
}

实际代码

/**
 * 渠道活动创建策略接口
 *
 */
public interface ActivityStrategy {
 
    /**
     * 创建渠道活动配置
     *
     * @param param 活动参数
     * @return 
     */
    void createActivity(ActParam param);
}
 
/**
 * 活动模板类
 *
 */
@Slf4j
public abstract class AbstractActivityTemplate implements ActivityStrategy {
 
    /**
     * 抽象方法:具体渠道活动创建
     *
     */
    protected abstract ProcessResult createChannelActivity(ActParam param);
 
    @Override
    public ProcessResult createActivity(ActParam param) {
         //保存活动基础信息
         saveActBaseConfig(param);
         //保存具体渠道配置
         createChannelActivity(param);
         //审计日志上报 ...
         //消息通知 ...
    }
}
 
/**
 * 短信推送策略具体实现类
 *
 */
@Component
public class SmsChannelActivityStrategy extends AbstractActivityTemplate {
    @Override
    public void createChannelActivity(ActParam param) {
        //保存短信配置数据
    }
}
(其他渠道活动类似,此处省略)
 
 
/**
 * 策略调用入口
 *
 */
@Slf4j
@Component
public class ActivityContext {
 
   @Resource
   private ActivityStrategyFactory activityStrategyFactory ;
 
      public void create(ActParam param) {
            //通过前面的工厂模式的代码,获取具体渠道对应的策略类
            ActivityStrategy strategy = activityStrategyFactory.getActivityStrategy(param.ChannelType);
            //执行策略
            strategy.createActivity(param);
      }
}

实际编码过程中,我们加入了ChannelActivityStrategy作为渠道活动创建策略接口,并用模板类AbstractActivityTemplate实现该接口,同时结合工厂模式创建具体策略,至此将三种模式结合了起来

实践总结

策略模式在项目开发过程中经常用于消除复杂的if else复杂逻辑,后续如果有新的渠道活动时,只需要新增对应渠道的活动创建逻辑即可,可以十分便捷地对系统业务进行扩展。

在项目实践过程,经常会将工厂模式、模板方法模式和策略模式一起结合使用。模板方法模式进行业务流程公共骨架的抽取,策略模式进行具体子流程策略的实现和调用的封装,而工厂模式可以进行子流程策略的创建。

多种模式的结合使用可以充分发挥出各个模式的优势,达到真正提升系统设计扩展性的目的。

3.2 运营活动执行

3.2.1 状态模式

具体场景

在运营活动的执行过程中,会涉及活动状态的变更,以及变更前的条件检测和变更后的操作处理。与之相对应地,我们很容易就会想到状态模式。

模式分析

在 GoF 经典的《设计模式:可复用面向对象软件的基础》中:状态模式允许一个对象在其内部状态改变的时候改变其行为。

状态模式的作用就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能。它们的关系可以描述为:状态决定行为。由于状态是在运行期被改变的,因此行为也会在运行期随着状态的改变而改变。

典型代码示例

/**
 * 状态模式
 * 抽象状态类
 * */
interface State {
    //状态对应的处理
    void handle()
}
 
  
//具体状态关现类
public  class ConcreteStateA implements  State {
    @Override
    public void handle() {
    }
}
 
public  class ConcreteStateB implements  State {
    @Override
    public void handle() {
    }
}
 
//环境类Context,访问入口
public class Context {
    //持有一个State类型的对象实例
    private State state;
 
    public void setState(State state) {
        this.state = state;
    }
      
    public void request() {
        //转调state来处理
        state.handle();
    }
}
 
public class Client {
    public static void main(String[] args){
        //创建状态
        State state = new ConcreteStateB();
        //创建环境
        Context context = new Context();
        //将状态设置到环境中
        context.setState(state);
        //请求
        context.request();
    }
}

实践总结

在实际软件项目开发中,业务状态不多且状态转移简单的场景, 可使用状态模式来实现;但如果是涉及的业务流程状态转移繁杂时,使用状态模式会引入非常多的状态类和方法,当状态逻辑有变更时,代码也会变得难以维护,此时使用状态模式并不十分适合。

而当流程状态繁多,事件校验和触发执行动作包含的业务逻辑比较复杂时,如何去实现呢?

这里我们必须停下来思考:使用设计模式只是解决实际问题的一种手段,但设计模式不是一把“万能的”锤子,需要清楚地了解到它的优势和不足。而这种问题场景下,业界已经有一个更通用的方案——有限状态机,通过更高层的封装,提供给业务更便捷的应用。

3.2.2 状态模式的应用——有限状态机

有限状态机(Finite-State Machine , 缩写:FSM),业界简称状态机。它亦是由事件状态动作 三大部分组成,三者的关系是:事件触发状态的转移,状态的转移触发后续动作的执行。状态机可以基于传统的状态模式硬编码来实现,也可以通过数据库/文件配置或者DSL的方式来保存状态及转移配置来实现(推荐)。

业界中也已涌现出了不少开源状态机的框架,比较常用的有Spring-statemachine(Spring官方提供) 、squirrel statemachine和阿里开源的cola-statemachine。

实际应用

在实际项目开发中,我们针对自身业务的特点:业务流程状态多,但是事件触发和状态变更动作相对简单,故而选择了无状态、更加轻量级的解决方案——基于开源的状态机实现思想进行开发。(关于状态机的实现和使用选型会在后续的文章中做进一步的分析,感兴趣的童鞋可以访问官网先做了解)。

实践代码

/**
 * 状态机工厂类
 */
public class StatusMachineEngine {
    private StatusMachineEngine() {
    }
    private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap();
 
    static {
        //短信推送状态
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine");
        //PUSH推送状态
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine");
        //......
    }
 
    public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) {
        return STATUS_MACHINE_MAP.get(channelTypeEnum);
    }
 
   /**
     * 触发状态转移
     * @param channelTypeEnum
     * @param status 当前状态
     * @param eventType 触发事件
     * @param context 上下文参数
     */
    public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) {
        StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum));
        //推动状态机进行流转,具体介绍本期先省略
        orderStateMachine.fireEvent(status, eventType, context);
    }
 
/**
 * 短信推送活动状态机初始化
 */
@Component
public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> {
 
    @Autowired
    private  StatusAction smsStatusAction;
    @Autowired
    private  StatusCondition smsStatusCondition;
 
    //基于DSL构建状态配置,触发事件转移和后续的动作
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        StateMachineBuilder<String, EventType, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(INIT)
                .to(NOT_START)
                .on(EventType.TIME_BEGIN)
                .when(smsStatusAction.checkNotifyCondition())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(NOT_START)
                .to(DATA_PREPARING)
                .on(EventType.CAL_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        builder.externalTransition()
                .from(DATA_PREPARING)
                .to(DATA_PREPARED)
                .on(EventType.PREPARED_DATA)
                .when(smsStatusCondition.doNotifyAction())
                .perform(smsStatusAction.doNotifyAction());
        ...(省略其他状态)
        builder.build(StatusMachineEngine.getMachineEngine(ChannelTypeEnum.SMS));
    }
 
   //调用端
   public class Client {
     public static void main(String[] args){
          //构建活动上下文
          Context context = new Context(...);
         // 触发状态流转
          StatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context);
      }
   }
}

通过预定义状态转换流程的方式,实现ApplicationListener接口,在应用启动时将事件、状态转移条件和触发操作的流程加载到状态机工作内存中,由事件触发驱动状态机进行自动流转。

实践总结

实际场景中,不必强行套用设计模式,而是应当充分结合业务的特点,同时针对设计模式的优劣势,进行更加合适的选型或者进一步扩展。

3.3 自动化运营活动审批

3.3.1 设计模式的综合应用——工作流引擎

具体场景

为了做好品质和风险管控,活动创建需要加入审批环节,把控运营活动的发布执行,同时对于不同类型的运营活动,可能涉及的业务领域和部门各不相同,审批管控人员也不一样,需要配置相对应的审批关系。

此时需要做到:

  • a.审批流程全配置化,易修改和添加;
  • b.业务流程节点可自由编排,组件公用化;
  • c.流程数据持久化,审批过程数据需要进行操作监控。

针对这方面的需求,业界有一套通用的业务工具——工作流引擎。工作流引擎显然并不属于具体某一种设计模式的实现,它是涵盖了多种设计模式的组件应用。

不仅仅是审批功能,其实前面自动化营销流程引擎设计也同样是使用工作流引擎搭建流程组件

状态机 VS 工作流引擎

工作流引擎和状态机似乎存在非常多的相似之处,都可以通过定义流程的节点、转移条件和相应触发的操作来完成业务流程。如果只从适用场景的复杂性上看,状态机更适用于单维度的业务问题,能够清晰地描绘出所有可能的状态以及导致转换的事件,更加灵活轻便;而工作流引擎则更适合业务流程管理,解决如大型CRM复杂度更高的流程自动化问题,可以改善整体业务流程的效率。

在业界的工作流引擎中,比较著名的有Activiti和JBPM等。(关于状态机和工作流引擎的对比、开源工作流引擎的具体介绍和选型,以及如何自行开发构建一款基本的工作流引擎组件,同样是会在后续的文章中做进一步分析,本文由于主题和篇幅的原因暂不做详细介绍。)

在实际开发过程中,我们是基于开源的Activiti工作流引擎自研了一套简易版的工作流引擎,精简了许多相关的配置,只留下了核心流程操作和数据记录。

工作流引擎流程图:

实践总结

工作流引擎是涵盖了多种设计模式的应用组件,只有在复杂多变的业务场景中才需要应用,需要结合业务进行仔细评估。在合适的场景使用合适的解决方案,遵循系统架构设计的简单、合适、可演化原则,不过度设计。

四、总结

本文基于自动化营销的业务实践,分析介绍了工厂方法模式、模板方法模式、策略模式以及状态模式这四种模式在项目开发中的具体实现过程。也在单纯的模式之外介绍了状态机和工作流引擎这些涵盖了多种设计模式系统组件,并分享了过程中的选择和思考。

面对业务复杂多变的需求,需要时刻关注系统设计的复用性和可扩展性,而设计原则和设计模式可以在系统设计实现时给予我们方向性的指导,同时更需要根据实际业务场景进行合理的选择,合适的变通,不断完善自己的方法论。

后续我们将带来系列专题文章的其他内容,每一篇文章都会对里面的技术实践进行详尽解析,敬请期待。

作者:vivo互联网服务器团队-Chen Wangrong

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

领域驱动设计(DDD:Domain-Driven Design) - 解道Jdon

mikel阅读(1171)

来源: 领域驱动设计(DDD:Domain-Driven Design) – 解道Jdon

领域驱动设计(DDD:Domain-Driven Design)

Eric Evans的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法,本站Jdon.com是国内公开最早讨论DDD网站之一,可订阅DDD专题。初学者学习DDD可从研究本站Jdon框架的DDD应用源码开始,戳这里开始

过去系统分析和系统设计都是分离的,正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。见下面DDD与传统CRUD或过程脚本或者面向数据表等在开发效率上比较:

ddd

服务器后端发展三个阶段:

  1. UI+DataBase的两层架构,这种面向数据库的架构(上图table module )没有灵活性。
  2. UI+Service+DataBase的多层SOA架构,这种服务+表模型的架构易使服务变得囊肿,难于维护拓展,伸缩性能差,见这里讨论Spring Web 应用的最大败笔垂直切片的烟囱式故事已经一去不复返了
  3. DDD+SOA微服务的事件驱动的CQRS读写分离架构,应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。

DDD革命性在于:领域模型准确反映了业务语言,而传统J2EE或Spring+Hibernate等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被比喻成失血模型,那么领域模型这种带有业务方法的充血模型到底好在哪里?

比赛Match为案例,比赛有“开始”和“结束”等业务行为,但是传统经典的方式是将“开始”和“结束”行为放在比赛的服务Service中,而不是放在比赛对象本身之中。我们不能因为用了计算机,用了数据库,用了框架,业务模型反而被技术框架给绑架,就像人虽然是由母亲生的,但是人的吃喝拉撒母亲不能替代,更不能以母爱名义肢解人的正常职责行为,如果是这样,这个人就是被母爱绑架了。

提倡充血模型,实际就是让过去被肢解被黑crack的业务模型回归正常,当然这也会被一些先入为主或被洗过脑的程序员看成反而不正常,这更是极大可悲之处。看到领域模型代码,就看到业务需求,没有翻译没有转换,保证软件真正实现“拷贝不走样”。

DDD最大的好处是:接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离。DDD让你首先考虑的是业务语言,而不是数据。重点不同导致编程世界观不同。

DDD是解决复杂中大型软件的一套行之有效方式,在国外已经成为主流。DDD认为很多原因造成软件的复杂性,我们不可能避免这些复杂性,能做的是对复杂的问题进行控制。而一个好的领域模型是控制复杂问题的关键。领域模型的价值在于提供一种通用的语言,使得领域专家、产品经理和软件技术人员联系在一起,沟通无歧义。

DDD落地实现离不开Clean架构、六边形架构、 CQRS、Event Source几大大相关领域。下图是传统以数据库为中心的架构与使用DDD实现以领域为中心架构的区别。

DDD专门为解决复杂性而诞生,因此解决思路完全不同于传统的CRUD,但是DDD本身掌握起来并不会感觉复杂,从程序员角度看,DDD其实是研究将包含业务逻辑的ifelse语句放在哪里的学问。

DDD主要难点是领域发现和领域建模,万事开头难,除了DDD原著作提出领域统一语言外,目前用于领域发现的方法有:事件风暴、业务能力建模、领域讲故事、业务模型画布、示例映射、影响映射、Wardley Maps等,全球DDD社区为此做出主要贡献的人员名单(按Twitter名称排列):

    @ericevans0 创建了DDD
    • @ziobrando 发明了

事件风暴

    建模方法。
    @ntcoding 发明使用画布canvas 映射有界上下文方法。
    • @swardley 发明

WardleyMapping方法

    进行战略规划。
    • @mathiasverraes 提出了

事件溯

    源具体设计策略,提出复杂系统的仿真建模。
    • @gregyoung 提出

CQRS模式

    来分离模型。

重点资讯

  复杂软件设计之道:领域驱动设计全面解析与实战

  用事件风暴分解单体设计微服务 – capital

  函数式DDD架构入门 – SCOTT WLASCHIN

  DDD社区权威解读:领域驱动设计在2021年将会怎样?

 

教程与文章

板桥大话DDD
用大白话简单谈谈DDD的一些基础特点,只是扫盲!数据库SQL强人慎入

板桥DDD研究十年心得:《复杂软件设计之道:领域驱动设计全面解析与实战》
承蒙机械出版社厚爱

板桥:为什么DDD的Bounded Context翻译为”有界上下文”?

业务代码编程陷阱案例 – jaxenter
非常普遍的不恰当的编程方式,失血模型导致的陷阱

面向对象建模与数据库建模两种分析设计方法的比较
数据库驱动设计与对象建模是决定软件不同命运的两大派别,谁可以让软件更具有生命,维护拓展更方便?伸缩性更强?

面向对象与领域建模
据调查,目前有70%左右程序员是在使用OO语言编写传统过程化软件,缺乏完整的面向对象思维方法的教育和培训是基本根源,本文对软件开发中几个常见问题提出了独立的见解及尖锐的观点

Evans DDD 领域建模
如何提炼模型,而不是数据表,进而精化模型对象,使其能够反映领域概念基本本质是一个复杂过程,Evans DDD是2004年提出的具备革命性影响的软件思想。

实战DDD(Evans DDD:Domain-Driven Design领域驱动设计)
领域建模是一种艺术的技术,不是数学的技术,它是用来解决复杂软件快速应付变化的解决之道。

领域模型驱动设计(Evans DDD)之模型提炼

软件建模设计

如何从职责和协作中发现丰富的充血对象?
失血模型贫血模型是DDD最大敌人,如何根据SOLID原则GRASP原则设计业务行为?本文给出了DDD具体实践中一些具体细节,是和DDD配合一起进行面向对象分析设计的好方法。

业务模型统一描述
统一语言是DDD一个重要特征和重点。

DDD CQRS和Event Sourcing的案例:足球比赛
DDD + CQRS + Event Sourcing实现案例,结合代码与理论讲解。

集装箱车队系统的DDD案例
为上海某大型港口公司的运输系统实施的一个领域驱动设计DDD的实战咨询案例。

DDD仓储实现:Spring Data JDBC教程

不使用DDD的后果:为什么我们停止了向微服务的迁移?

使用DDD聚合发现隐藏的业务规则的案例分析:数据库事务的业务实现 

向领域驱动设计前进: 如何使用DDD实现从单体到微服务迁移打造业务平台或中台?

DDD+微服务大型案例:Uber如何从复杂的RPC微服务转向面向业务领域的微服务架构DOMA?

全球大型电商Shopify如何使用DDD实现单体架构的模块化?

最全面DDD微服务教程:SpringBoot + DDD + Apache Kafka实现最终一致性的教程与源码 – itnext

更多#DDD领域驱动设计专题、领域事件专题

DDD案例完整实现:本站开源Jivejdon 文档按这里

DDD领域驱动设计落地实践(十分钟看完,半小时落地) - 只会一点java - 博客园

mikel阅读(866)

来源: DDD领域驱动设计落地实践(十分钟看完,半小时落地) – 只会一点java – 博客园

一、引子

不知今年吹了什么风,忽然DDD领域驱动设计进入大家视野。该思想源于2003年 Eric Evans编写的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法。刚好公司领导强力推荐这个,抱着学习的心态,耗时5个月,体验了一把:“DDD从入门到弃坑”。

二、思想

学习网站:https://www.jdon.com/ddd.html

书:领域驱动设计

2.1 服务器后端发展三个阶段

 

服务器后端发展三个阶段:

  1. 面向过程脚本:初始简单,业务复杂后,维护难度指数上升。–>基本不为主流使用
  2. 面向数据库表:初始难度中,业务复杂后,维护难度延迟后再指数上升。—>目前市面上主流
  3. 面向业务模型:DDD+SOA微服务的事件驱动的CQRS读写分离架构:应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。初始难度高,业务复杂后,维护难度线性上升(已很不错)

2.2 DDD最大特点

DDD革命性在于:领域模型准确反映了业务语言,而传统微服务数据对象除了简单setter/getter方法外,没有任何业务方法,即失血模型,那么DDD领域模型就是充血模型(业务方法定义在实体对象中)

 

三、落地

3.1 领域模型设计

以渠道中心(一个微服务)作为例子来做领域模型设计,核心就是设计2个图,一个是战略设计图(宏观)  ,一个是战术设计图(细节)。

1.领域战略设计图

战略设计图是从一个限界上下文的角度出发去分析业务场景。主要是宏观上的核心域、子域、实体关系图。demo如下图:

2.领域战术设计图

战术设计图是从一个限界上下文的角度出发去分析业务场景。细化到核心业务字段、领域实体、值对象、领域服务、领域事件等等。基本上这个图画完,代码已经知道怎么写了。demo如下图:

3.2 技术实现

整体项目框架分层图如下所示:

如上图,4层典型DDD分层结构,

1.展现层:controller层。无业务逻辑

2.应用服务层:此层可以包含查询逻辑,但核心业务逻辑必须下沉到领域层。

3.领域服务层:业务在这里组装。仓储(资源库)接口在此层定义。

4.基础设施层:仓储(资源库)实现层+PO持久化层。

注:

1.简单查询不涉及业务,是可以直接从应用层直接穿透到PO查询,不需要经过domain层。如下图所示,DDD本身是不限制非业务类操作跨层调用的。

 

2.DTO是不能存在于domain层的,DDD设计不认为DTO是业务对象,entity才是。或者传值简单数据类型也是可以的。

3.2.1 服务调用问题

1.域内调用

领域内调用,随便调用,丝般顺滑。至于实现,可以由一个核心域的仓储实现层(第四层)去实现多个Repository接口。(比如这里A是核心域的实体名,B是支撑域、通用域等)

 2.跨域调用

 

 

跨域分为

  • 1.同上下文跨域:ACL层->Adapter适配器层→调用其它域的repository。—>不得已才使用,不推荐使用。
  • 推荐:1.使用领域事件 eventbus来做解耦(nest-eventbus使用

               2.考虑是否有可能合并为一个领域.

  • 2.跨上下文(肯定跨域):ACL层->Adapter适配器层->feign调用

3.2.2 包结构

包结构如下:

 展开包结构如下:

展现层:Controller,仅做接口的入口定义和编排转发,不做任何的业务处理;

应用服务层:application,负责接口参数DTO的简单校验,以及DTO和实体值对象的数据转换,对于简单的业务,也可以在应用层加载实体直接执行实体行为方法;

领域层:

  • 模型:根据领域模型分析领域内各实体、聚合、聚合根、值对象等,这些对象在*.domain.model定义,实体内的行为方法只负责维护实体自身的生命周期和状态;
  • 行为:领域内各实体、聚合、聚合根等,会有相应的行为,在*.domain.model包下定义行为方法;
  • 领域服务:领域提供的接口服务,需要定义在*.domain.service包下,业务相关的前置业务判断、多个实体或值对象的行为逻辑处理等,都在领域服务中实现,需要注意的是并不是每个实体都有一个对应的领域服务,但是依赖多个实体的行为方法,最好根据这个业务模块是建立一个领域服务;
  • 仓储:领域服务或上层应用服务需要使用到的基础设施层,包括DB、Feign调用等,定义在*.domain.repository下,在*.infrastructure.repository下实现;

适配层:在acl包下的feign定义依赖外部的接口,并在acl的adapter包编写转换,由仓储层操作实体时调用;

持久层:与常用DAO定义一致,由仓储层操作实体时调用。

 3.2.3 技术架构

目前业内没有标杆,github开源地址:https://github.com/jovezhao/nest  。作者不是本人哈,这个项目可以练手DDD。

 

四、总结

DDD可以尝试,但不建议主流业务硬上。建议浅尝即止。(据我所知,业内连阿里巴巴都不敢上。)

开源框架 - 新 代码生成器 WebFirst / .NET Core - 果糖大数据科技 - 博客园

mikel阅读(1107)

来源: 开源框架 – 新 代码生成器 WebFirst / .NET Core – 果糖大数据科技 – 博客园

框架描述

WebFirst  是一新代的 代码生成器,用法简单,功能强大,支持多种数据库 ,具体功能如下:

一、 建库、CodeFirst方式在线建表,没用到CodeFirst的用户可以用工具轻松体验,支持公共字段

二、导出EXCEL文档,把每个表的数据导出来

三、模版管理 可以自个添加修改模版,使用的是Razor模版引擎对C#程序员更加友好

四、方案管理,可以创建自已的生成方案,修改方案

五、支持扩展模版属性,支持生成更加丰富的前端代码

六、支持生成解决方案

七、支持生成附加文件,支持文件后缀

八、支持视图

九、支持自定义数据类型

十、支持多种数据库 MYSQL PGSQL SQLITE SQLSERVE  ORCLE  达梦

代码生成器的使用场景

一、提高工作效率

我们都知道一个CRUD其实如果有代码生成器的话只需要几分钟就可以把完整的功能做完,如果用手去写那么可能就要花掉一整天的时间,多出来的这些时间学学技术多好

二、大量重复有共性的功能

使用代码生成器最重要的就是减少大量重复的工作,比如我们有 数据库model 有表单model 有Grid绑定的 model  光写这些MODEL就很头痛,要写各种特性

 

 WebFirst管理和配置功能介绍

 方案管理

  方案是生成的一些配置比如用哪个模版、生成路径、文件名、命名空间等等进行分类和存储 方便下次使用

  内置自带的方案可供学习

 

 

  模版管理

模版是方案的一个属性,创建方案必须选择模版,在WebFirst中我们使用的是Razor模版引擎,功能非常强大,带有智能提示

 

WebFirst专门提供了一个模版编写的小程序让你有智能提示的 点点点,把模版轻松写出来

特色功能

1、在线版CodeFirst

比手动建实体生成表速度更快,只需界面操作选择C#类型(类型可以自定义配置)就能把100个字段的表在几分钟建出来,并且生成表,这种效率手写类根本做不到

2. 快速生成文档

生成的excel文档,比一般的软件生成出来的可能更友好点吧,虽然功能比较一般不过,作为代码生成器+快速开发还是不错的功能

3、支持数据源建类

有的时候可能需要,通过算法或者写个SQL把实体建出来

 

 4、支持生成的时候追附加文件

比如我们要生成项目文件,仓储 ,或者JSON文件等

 

 

5、自定义实体属性配置

这个功能就比较强大了,比如我要给字段设置数据源,控件类型,这样就需要自定义扩展属性进行配置,并且在模版中可以使用这些自定义的特性,每个字段支持多个自定义属性

 

6、自带方案用例

可以先用自带的用方案生成出来看看效果如何,然后改改路径、改改模版就能用到自个的项目中了

 

7、支持备份和还原

将EXE目录下的文件 database文件夹里面的数据库复制进行备份

 

粘贴到生成的项目中

 

我们把EXE目录里面database中的库替换成备份库,这样就实现了还原功能

 

 

下载地址

GITHUB  https://github.com/donet5/WebFirst   实实在在的干货 幸苦开发2个月,感兴趣的可以GITHUB 点个赞

另外我们的另一个开源作品【SqlSugar】 也更新了联表查询语法

1
2
3
4
5
6
var query5 = db.Queryable<Order>()
            .LeftJoin<Custom>((o, cus) => o.CustomId == cus.Id)
            .Where(o => o.Id == 1) 
            .Select((o, cus) => new  { o,cus })
            .ToList(); //现在的语法可以说是相当美观,并且你用where的时候 可以只写一个 o而不是所有都要加上 (o,cus)
//其它框架基本上要  <br>//db.Queryable<Order,Custom>().LeftJoin<Custom><br>//比较下来SqlSugar是不是会更简洁一些呢  

要另外SqlSugar将于10月底支持自动化分表功能5年多的沉淀和设计 最简、最好用、最实用,SqlSugar在2021年和2020可以说不是一框架,

以前用过认为不好用或者没用的也可以关注一下,因为2021开发时间可以等于以前几年开发时间,坚持创新 完美

GITHUB: https://github.com/donet5/SqlSugar   也可以关注一下

MongoDB查询转对象时出错 Element '_id' does not match any field or property of class - 狼窝窝 - 博客园

mikel阅读(740)

来源: MongoDB查询转对象时出错 Element ‘_id’ does not match any field or property of class – 狼窝窝 – 博客园

参考:https://www.cnblogs.com/94cool/p/6230202.html

解决方法:

1、在实体类加:[BsonIgnoreExtraElements]

2、或者定义public ObjectId _id { get; set; }

例子:

[BsonIgnoreExtraElements]

public class BaseData
{
//public ObjectId _id { get; set; }

public string cNo { get; set; }

public string customer { get; set; }

public long batchNo { get; set; }

public DateTime mDate { get; set; }

public string mUser { get; set; }
}

 

顺便保存下数据库帮助类

NuGet需要引用:MongoDB.Driver;

数据库管理工具可以用Robo 3T或者Studio 3T

复制代码
public class MongoDBHelper
    {
        #region 构造函数(初始化集合,子类需重写集合名)
        /// <summary>
        /// 集合
        /// </summary>
        public string _collName { get; set; }
        public MongoDBHelper(string collName)
        {
            this._collName = collName;
        }
        #endregion

        #region 连接配置
        /// <summary>
        /// 链接
        /// </summary>
        private static readonly string conneStr = "mongodb://127.0.0.1:27017";
        /// <summary>
        /// 数据库
        /// </summary>
        private static readonly string dbName = "Testdb";
        #endregion

        #region 单例创建链接
        private static IMongoClient _mongoclient { get; set; }
        private static IMongoClient CreateClient()
        {
            if (_mongoclient == null)
            {
                _mongoclient = new MongoClient(conneStr);
            }
            return _mongoclient;
        }
        #endregion

        #region 获取链接和数据库

        private IMongoClient client = CreateClient();
        public IMongoDatabase _database { get { return _mongoclient.GetDatabase(dbName); } }

        public IMongoDatabase GetDatabase()
        {
            return _database;
        }
        public IMongoCollection<T> GetClient<T>() where T : class, new()
        {
            return _database.GetCollection<T>(_collName);
        }
        #endregion

        #region +InsertMany 批量插入
        /// <summary>
        /// 批量插入
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="t">实体集合</param>
        /// <returns></returns>
        public int InsertMany<T>(List<T> t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                client.InsertMany(t);
                return 1;
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
        #endregion

        #region +InsertManyAsync 异步批量插入
        /// <summary>
        /// 异步批量插入
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="t">实体集合</param>
        /// <returns></returns>
        public async Task<int> InsertManyAsync<T>(List<T> t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                await client.InsertManyAsync(t);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
        #endregion
        
        #region +Add 添加一条数据
        /// <summary>
        /// 添加一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public int Add<T>(T t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                client.InsertOne(t);
                return 1;
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
        #endregion

        #region +AddAsync 异步添加一条数据
        /// <summary>
        /// 异步添加一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public async Task<int> AddAsync<T>(T t) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                await client.InsertOneAsync(t);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
        #endregion

        #region +Update 修改一条数据
        /// <summary>
        /// 修改一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public UpdateResult Update<T>(T t, string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (item.Name.ToLower() == "id") continue;
                    list.Add(Builders<T>.Update.Set(item.Name, item.GetValue(t)));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return client.UpdateOne(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateAsync 异步修改一条数据
        /// <summary>
        /// 异步修改一条数据
        /// </summary>
        /// <param name="t">添加的实体</param>
        /// <param name="host">mongodb连接信息</param>
        /// <returns></returns>
        public async Task<UpdateResult> UpdateAsync<T>(T t, string id, bool isObjectId) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (item.Name.ToLower() == "id") continue;
                    list.Add(Builders<T>.Update.Set(item.Name, item.GetValue(t)));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return await client.UpdateOneAsync(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateManay 批量修改数据
        /// <summary>
        /// 批量修改数据
        /// </summary>
        /// <param name="dic">要修改的字段</param>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">修改条件</param>
        /// <returns></returns>
        public UpdateResult UpdateManay<T>(Dictionary<string, string> dic, FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                T t = new T();
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (!dic.ContainsKey(item.Name)) continue;
                    var value = dic[item.Name];
                    list.Add(Builders<T>.Update.Set(item.Name, value));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return client.UpdateMany(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region +UpdateManayAsync 异步批量修改数据
        /// <summary>
        /// 异步批量修改数据
        /// </summary>
        /// <param name="dic">要修改的字段</param>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">修改条件</param>
        /// <returns></returns>
        public async Task<UpdateResult> UpdateManayAsync<T>(Dictionary<string, string> dic, FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                T t = new T();
                //要修改的字段
                var list = new List<UpdateDefinition<T>>();
                foreach (var item in t.GetType().GetProperties())
                {
                    if (!dic.ContainsKey(item.Name)) continue;
                    var value = dic[item.Name];
                    list.Add(Builders<T>.Update.Set(item.Name, value));
                }
                var updatefilter = Builders<T>.Update.Combine(list);
                return await client.UpdateManyAsync(filter, updatefilter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region Delete 删除一条数据
        /// <summary>
        /// 删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectId</param>
        /// <returns></returns>
        public DeleteResult Delete<T>(string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                return client.DeleteOne(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteAsync 异步删除一条数据
        /// <summary>
        /// 异步删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectId</param>
        /// <returns></returns>
        public async Task<DeleteResult> DeleteAsync<T>(string id, bool isObjectId = true) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //修改条件
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                return await client.DeleteOneAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteMany 删除多条数据
        /// <summary>
        /// 删除一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">删除的条件</param>
        /// <returns></returns>
        public DeleteResult DeleteMany<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return client.DeleteMany(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region DeleteManyAsync 异步删除多条数据
        /// <summary>
        /// 异步删除多条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">删除的条件</param>
        /// <returns></returns>
        public async Task<DeleteResult> DeleteManyAsync<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return await client.DeleteManyAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        #endregion

        #region FindOne 根据id查询一条数据
        /// <summary>
        /// 根据id查询一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectid</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <returns></returns>
        public T FindOne<T>(string id, bool isObjectId = true, string[] field = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));  //默认id为objectid类型
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    return client.Find(filter).FirstOrDefault<T>();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                return client.Find(filter).Project<T>(projection).FirstOrDefault<T>();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindOneAsync 异步根据id查询一条数据
        /// <summary>
        /// 异步根据id查询一条数据
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="id">objectid</param>
        /// <returns></returns>
        public async Task<T> FindOneAsync<T>(string id, bool isObjectId = true, string[] field = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                FilterDefinition<T> filter;
                if (isObjectId)
                {
                    filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));
                }
                else
                {
                    filter = Builders<T>.Filter.Eq("_id", id);
                }

                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    return await client.Find(filter).FirstOrDefaultAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                return await client.Find(filter).Project<T>(projection).FirstOrDefaultAsync();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindList 查询集合
        /// <summary>
        /// 查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public List<T> FindList<T>(FilterDefinition<T> filter = null, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);

                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null)
                        return client.Find(filter).ToList();
                    //进行排序
                    return client.Find(filter).Sort(sort).ToList();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                if (sort == null) return client.Find(filter).Project<T>(projection).ToList();
                //排序查询
                return client.Find(filter).Sort(sort).Project<T>(projection).ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListAsync 异步查询集合
        /// <summary>
        /// 异步查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public async Task<List<T>> FindListAsync<T>(FilterDefinition<T> filter, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return await client.Find(filter).ToListAsync();
                    return await client.Find(filter).Sort(sort).ToListAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();
                if (sort == null) return await client.Find(filter).Project<T>(projection).ToListAsync();
                //排序查询
                return await client.Find(filter).Sort(sort).Project<T>(projection).ToListAsync();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListByPage 分页查询集合
        /// <summary>
        /// 分页查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="count">总条数</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public List<T> FindListByPage<T>(FilterDefinition<T> filter, int pageIndex, int pageSize, out long count, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                count = client.CountDocuments(filter);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
                    //进行排序
                    return client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();

                //不排序
                if (sort == null) return client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();

                //排序查询
                return client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region FindListByPageAsync 异步分页查询集合
        /// <summary>
        /// 异步分页查询集合
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">查询条件</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="field">要查询的字段,不写时查询全部</param>
        /// <param name="sort">要排序的字段</param>
        /// <returns></returns>
        public async Task<List<T>> FindListByPageAsync<T>(FilterDefinition<T> filter, int pageIndex, int pageSize, string[] field = null, SortDefinition<T> sort = null) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                //不指定查询字段
                if (field == null || field.Length == 0)
                {
                    if (sort == null) return await client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
                    //进行排序
                    return await client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
                }

                //制定查询字段
                var fieldList = new List<ProjectionDefinition<T>>();
                for (int i = 0; i < field.Length; i++)
                {
                    fieldList.Add(Builders<T>.Projection.Include(field[i].ToString()));
                }
                var projection = Builders<T>.Projection.Combine(fieldList);
                fieldList?.Clear();

                //不排序
                if (sort == null) return await client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();

                //排序查询
                return await client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region Count 根据条件获取总数
        /// <summary>
        /// 根据条件获取总数
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">条件</param>
        /// <returns></returns>
        public long Count<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return client.CountDocuments(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region CountAsync 异步根据条件获取总数
        /// <summary>
        /// 异步根据条件获取总数
        /// </summary>
        /// <param name="host">mongodb连接信息</param>
        /// <param name="filter">条件</param>
        /// <returns></returns>
        public async Task<long> CountAsync<T>(FilterDefinition<T> filter) where T : class, new()
        {
            try
            {
                var client = _database.GetCollection<T>(_collName);
                return await client.CountDocumentsAsync(filter);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion
    }
复制代码

 

代码地址:

https://github.com/bill1411/mybase/tree/master/Solution/Mongo

从Vehicle-ReId到AI换脸,应有尽有,解你所惑 - 周见智 - 博客园

mikel阅读(846)

来源: 从Vehicle-ReId到AI换脸,应有尽有,解你所惑 – 周见智 – 博客园

最近在做视频搜索的技术调研,已经初步有了一些成果输出,算法准确性还可以接受,基本达到了调研的预期。现将该技术调研过程中涉及到的内容总结一篇文章分享出来,内容比较多,初看起来可能关系不大,但是如果接触面稍微广一些,就会发现其实原理都是差不多的。

先描述一下我要解决的问题:上传任意一个车辆截图,需要从海量的监控视频中(高速监控)找到该车辆目标历史经过点位的历史视频录像。这个问题本质上其实就是图像检索或者叫Object-ReId问题,唯一不同的是,找到车辆目标后需要定位到视频录像,后者其实很简单,只需要事先建立好图片和录像片段之间的索引关系即可,跟我们今天要讨论的内容关系不大。(本文图片点击查看更清楚)

 

图像检索的本质

首先要清楚的是,机器是无法直接理解图像或者声音这种多媒体数据的,甚至也包括一些复杂的结构化数据(比如数据库中的表数据)。传统机器学习中一个常见的概念是“特征工程”,说的是从原始的、复杂的数据中提取出有一定代表意义的特征数据(简单表示,比如用多维向量),这些特征数据相比原数据要简单得多!然后再用算法去分析、学习这些特征数据,得出规律。基于神经网络的深度学习中已经慢慢弱化了“特征工程”这一概念,因为深度学习主流的方式基本都是端到端的流程,输入直接产生输出,特征提取的过程已经在神经网络中的某个部分帮你做完了。

那么现在图片检索的问题,其实已经被转变成“特征数据检索的问题”了。原来需要进行图像比对,现在只需要进行特征比对,而显然机器更擅长后者。

 

Object-ReId/Person-ReId/Vehicle-ReId的原理

ReId技术一般用于多摄像机目标重识别的场合,目标经过多个点位被多个摄像机拍摄录像存储,输入该目标的一张截图,可以利用ReId的技术将该目标经过的点位找出来,用于后续的运行轨迹分析,该技术一般用于安防/公安领域,目标一般可以是行人(Person-ReId)和车辆(Vehicle-ReId)。ReId的核心就是前面提到的图像检索技术,从海量图片中(视频由图片组成)检索指定的图片,那么这个检索的准确性就依赖于前面提到的特征比对算法准确性了。

上图描述了Vehicle-ReId的一个完整流程,我们可以看到特征比对只是其中的一个环节,完整的流程还要包括车辆目标提取(目标检测)、特征提取、索引建立。

 

图像特征提取

前面已经知道了图像检索的本质其实就是特征的比对,那么这个特征应该如何提取得到呢?

传统的机器学习可能需要手工设计算法去提取特征,提取的方式有多种多样,拿图像而言,可以从颜色角度入手,提取图像的颜色特征。比如大部分人可能比较熟悉的颜色直方图,这个算法就可以用来计算图像的“像素组成”(比如各种颜色分别占比多少),像素组成确实可以在一定程度上代表原始图片。在某些数据集中,像素组成相似的原始图片也比较相似。然后拿像素组成数据去做分类/聚类,基本就可以搞定这种机器学习任务。

现在流行的深度学习已经抛弃了人工提取特征的做法,取而代之的是直接上神经网络。还是拿图像而言,直接用卷积网络无脑卷一卷,就可以得到对应的图像特征。这种方式提取到的特征是unreadable的,不像像素组成,它确实可以被人理解。卷积网络最后提取到的特征人工无法直观理解,它可能仅仅是一个高维向量,不做处理的话,你都无法在二维/三维空间中显示出来。所以很多人说深度学习(神经网络)是不可解释的,在某种程度上它确实无法被解释。

由前面的内容我们不难发现,特征提取是非常重要的一步,直接关系到后面基于特征的一切应用的准确性。特征是对原始数据的一种表达,是计算机容易识别的一种理想格式。理想情况下,特征之间的特性和规律可以直接反应原始数据之间的特性和规律。传统机器学习过程中,如何找到合适的特征提取方法是一项非常难的事情,现在主流的深度学习过程中,已经简化了该步骤。

需要注意的是,一些论文、博客、文章中对特征的称呼不尽相同,比如Features(特征)/Representation(表达或表示)/Embedding(嵌入)/Encoding(编码)等等基本都是一个意思(注意中文翻译可能不太准确)。其实从这些英文单词不难看出,不管用什么词,人们想要表达的意思大概都是差不多的,即特征是对原数据的简要表达。

上图是深度学习中利用神经网络来提取特征,原始神经网络是一个多分类网络,我们可以使用分类数据集去拟合该神经网络中的参数,待训练完毕后,去掉最上(最右)用于分类的网络层,倒数第二层即可输出128维的特征数据。基于这个128维的特征数据,我们可以做很多事情:

1、原网络做的分类任务。例子中原网络本身就是一个分类网络,对这些特征数据进行分类,推理出原输入图片的类型。看看是巩俐还是奥巴马;

2、本文的重点。特征数据比对,用于图像检索、人脸识别、Vehicle-ReId等;

3、用于无监督学习。先对一堆没有标签的图片数据集合进行特征提取,基于这些特征数据利用K-Means或DBSCAN等算法自动将这些图片分成若干类,类似Iphone相册自动分类功能(比如相同的人脸归为一类)。

总之,特征数据非常有用,是一切机器学习(深度学习)任务中的重中之重。

 

图像特征比对

前面已经多次提到特征比对,那么特征比对的方式有哪些呢?二维空间中的2个点,我们可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然);三维空间中的2个点,我们照样可以通过计算之间的直线距离来判断它们是否相似(越小越相似,为零表示完全相同。反之亦然)。那么对于更高维的点呢?照样可以用这种方式去做比较!

这里需要说的是,直线距离只是手段之一,还有其他距离可以计算。比如不太常见的余弦距离,它代表两个点到坐标原点线段之间的夹角余弦值,角度越小代表2点距离越近。余弦距离跟直线距离不同,一个是用角度大小衡量、一个是用线段长短衡量。

我们可以看到,直线距离(欧氏距离)关注点是2个特征的各个维度具体数值,而余弦距离关注点是2个特征的维度分布。直线距离为零,代表2个特征的各个维度数值完全相同;而余弦距离为零,代表2个特征的维度分布完全相同。(1, 1, 1)和(2, 2, 2)两个特征的直线距离不为零,因为它们各个维度的数值不同,但是它们的余弦距离为零,因为它们的维度分布是完全一样的,都是1:1:1。

举一个实际的例子,张三的语数外三科的成绩为(80, 80, 80),李四的语数外三科的成绩为(90, 90, 90),这两的直线距离不为零,李四的三科成绩明显跟张三不同。但是这两的余弦距离为零,可以理解为李四的三科平衡程度跟张三一致,都不偏科。所以不同的距离代表含义不同,直线距离可以用来衡量他们的成绩是否差不多,而余弦距离则可以用来衡量他们偏科程度是否差不多。两个距离,视角不一样。

 

高维特征降维和可视化

前面举例子用的是二维或者三维数据,其实特征数据大部分时候都是高维的,比如128维或1024维等等。在不做任何处理的情况下,我们无法直观看到这些高维数据之间的关系,因为它既不是二维的我们可以画到平面坐标系中、也不是三维的我们可以画在立体坐标系中。如果想要直观看到数据之间的关系,我们需要对这些特征再次进行降维处理,将之前的高维数据一一映射到二维或者三维空间。比如现在提取到了1000张图片的特征数据,每个数据都是128维,我们如果想要在二维或三维空间观察这1000个特征数据之间的关系(比如特征数据之间的紧密程度),从而判断原始图片之间的关系、或已经知道原始图片之间的关系我们需要验证提取到的特征数据是否合理。

值得高兴的是,已经有非常成熟的降维技术可以使用,比如常见的PCA和t-SNE算法,直接可以将高维数据降到二维或者三维,而依然保留原始数据的特性。通过这些手段我们可以直观看到高维特征数据在二维/三维空间中的呈现,从而观察原数据之间的关系。下图是我提取高速公路视频画面中车辆目标的特征数据,原始特征是128维,然后利用t-SNE算法进行降维处理,最后得到的二维格式数据并在二维坐标系中将原始图片一一对应绘制出来。

我们可以看到,外观相似的车辆(这些图片是随机抽取的,并没有标签数据)聚集在一起,用前面讲到的距离来说,就是越相似的图片特征距离越近。这个可视化的过程基本可以证明我前面设计的特征提取网络是合理的,这个网络用于提取其他类似车辆图片的特征也是OK的。

看到这里的朋友其实可能已经注意到,机器学习(或深度学习)的主要工作其实说白了就是一个不断对数据进行降维的过程,我们可以将原始非结构化数据诸如文字/图片/音频看成是一个维度很高(超高维)的数据格式,然后设计算法将这些超高维数据降到低维格式,再去应用。前面讲到的特征提取也算是降维的一种。

 

自编码器

谈到降维技术,这里我想介绍一个超级牛逼的结构,学名叫auto-encoder(翻译过来就是自编码器)。我刚开始接触这个东西的时候就感叹于它的神奇强大,因为它结构相当简单,而且理解起来并不费劲,但是起到的效果惊人。它的结构是对称的,前面半部分主要对输入(一般指图片)进行编码,其实就是特征提取,比如提取得到一个128维的特征数据。后半部分马上对该特征进行解码,还原成原来的图片。前半部分叫编码器,后半部分叫生成器。这个东西可以由两个神经网络组成,大概结构类似如下图:

如上图这种结构的神经网络训练也相当容易,你的训练数据集不需要提前标注,因为网络的输出就是网络的输入,换句话说,你可以把它当作无监督学习!有人可能就要问了,一编一解到底想要干什么呢?这样操作的主要目的是得到中间的特征数据(论文术语叫space representation),没错,用这种方式训练出来的前半部分可以当作一种特征提取器(原定义叫编码器),将它作用在其他类似图片数据上,就可以得到对应的特征数据,起到的作用跟前面介绍的其他特征提取方式差不多。

这种自编码器的一大优势是训练它的数据集合不需要标注,训练是一个无监督学习过程。它不像前面提到的那些特征提取方法,大部分都是基于监督学习的。也就是虽然我们的目的是训练一个特征提取的网络(网络输出是高维特征数据),但是往往需要提前准备带有标签的训练数据(如分类数据)。当然,除了这里提到的自编码器之外,还有其他的一些特征提取结构,也属于无监督学习的范畴,比如孪生网络、或者采用triplet loss训练时,这些都是无监督学习的例子。

 

AI换脸技术

这个话题其实跟今天谈到的特征数据(提取/比对)关系不是特别大,只是前面我已经提到了自编码器,知道了这个结构的前半部分能够应用于特征提取的任务,而刚才没说的是,它的后半部分(生成器)是可以用于AI换脸的,之前火爆全网的AI换脸可以采用类似技术实现。

其实AI换脸原理也非常简单,自编码器的前半部分用于人脸编码(特征数据,下同),它的后半部分基于该编码进行人脸还原(图像生成),这个过程即是我们进行网络训练的过程:一个人脸输入,不断拟合网络让它输出同一个人脸。如果我们在应用该网络结构的时候稍微改变一下:将A人脸输入到它的编码器,得到它的人脸编码后,不要使用对应的生成器去还原人脸,而是改用另外B人脸的生成器去还原人脸!那么会得到什么呢?答案是:得到一张A的脸部轮廓+B的五官细节。下图显示AI换脸的技术原理:

如上图可知,编码器输出的人脸编码在某种意义上可以看作是脸部轮廓的表示,生成器基于该轮廓进行五官细节恢复,最终得到一个合成后的人脸。下面是一个将赵本山五官换到杨澜脸部的例子(完整视频链接):

通过AI换脸的这个例子我们可以得知,特征提取相当重要,整个流程能够正常work(或work得很好)大部分依靠中间生成的特征数据(人脸编码)。神经网络的神奇之处就在于,有些东西你无法解释,但是就是凑效。

 

其他常见的无监督学习

既然提到了AI换脸,索性就将本篇文章的主题扯远一些。自编码器的训练过程属于无监督学习的范畴,根据相关大神的名言:无监督学习才是真正的人工智能。确实没错,监督学习在某些场合有非常多的局限性。那么除了上面提到的自编码器训练属于无监督学习,机器学习领域还有哪些无监督学习的例子呢?

1、类似K-Means这些聚类算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

2、类似t-SNE这种降维算法,算法可以自动从给定的数据(特征数据)寻找规律,无需事先提供参考样例

3、类似上面提到的自编码器,以及其他一些生成型网络,包括GAN相关技术,都属于无监督学习

4、类似采取triplet loss等技术直接操控特征数据的网络训练方式(基于特征数据计算loss),也属于无监督学习

只要在训练过程中无需事先提供参考样例(标注样本)的机器学习过程全部都可以看作是无监督学习,无监督学习跟算法并没什么直接关系,传统机器学习、现在主流基于神经网络的深度学习都可以有无监督学习方式。

好了,本篇文章到这里结束了。由于时间原因,以及查资料验证费时间,前前后后花了半个月功夫。其实主要目的是为了说明特征数据在机器学习(深度学习)领域的重要性,这个领域基本所有的东西全部围绕它展开的,所有的原始非结构化数据/结构化数据都需要先转成特征数据,再被机器学习算法(深度学习神经网络)学习。

肢体识别与应用 - 周见智 - 博客园

mikel阅读(891)

来源: 肢体识别与应用 – 周见智 – 博客园

肢体识别与应用

肢体识别本质上还是分类任务,该技术有很多应用场景,比如手势识别控制类应用、动作检测类应用、动作评测类应用、以及一些移动设备AR视频合成类应用。对于纯粹的检测类应用,可以直接对RGB源图像进行分类,当前基于CNN的图像分类技术已经非常成熟和稳定,准确性也完全达到了实用标准。而对于另外涉及到肢体评测、AR合成类的应用,单靠图像分类技术已经不太够了,需要用到关键点检测技术,它能检测出肢体关键点(比如四肢、面部纹理、手指等),然后基于检测到的关键点做进一步处理。关键点检测的原理其实跟分类技术原理差不多,只是神经网络的输出不太一样。

图像分类

在大部分深度学习入门教程中,图像分类基本就是hello world级别存在的,最常见的是识别猫还是狗。ImageNet比赛比的是1000分类,基本涵盖了大部分常见物体。图像分类的流程很简单,输入RGB图像,输出各分类的概率值,1000分类任务就输出1000个概率值。图像分类是深度学习技术在CV领域最基础的应用,主要原因是它涵盖了深度学习最重要的环节:特征自动提取。我们刚学习CNN时接触到的Alexnet、GoogleNet或者Resnet之类的,全部都是特征自动提取网络,如果需要对输入分类,网络后面再接特征分类层即可。

那么具体到肢体识别任务上,也可以将它看作一个图像分类任务,这里以‘剪刀石头布识别’为例子,是一个3分类的分类任务:

上面这张图大部分接触过深度学习的人应该很清楚,最基础的分类任务。图像分类起到的作用很有限,仅仅是对肢体做一个大概的分类,无法满足更细致的需求,接下来介绍关键点检测。

 

关键点检测

关键点检测的技术当前也非常成熟了,github上最早的OpenPose项目非常火爆,有几万星星。通过训练后,模型能够实时推理检测出人体关键点(四肢和面部,具体技术原理跟其他采用神经网络的深度学习技术相似,可以参考网络资料)。

关键点检测出来之后,可以进一步对这些关键点进行分析。分析结果就不仅仅是肢体识别了,还可以通过这些关键点相互之间的关系来判断肢体动作的协调程度、或者像一些AR应用在人体上叠加一些其他内容(类似抖音视频合成)。

 接下来介绍两种关键点分析方法,一种是基于传统机器学习,先从关键点中人工提取特征、然后再对特征进行分析(基于常见的机器学习算法去做分类、聚类或者回归之类的);另外一种是基于深度学习端到端的处理方式,关键点直接作为神经网络的输入,输出对应想要的结果。从这两种不同的处理方式中可以了解到传统机器学习和深度学习的区别,关于两者的区别可以看一下。

 

关键点+机器学习方式

机器学习的一大特点是需要人工提取原数据的特征,这个过程叫特征工程。如果需要对关键点进行分析,先要从这些关键点中提取高质量特征数据,为什么要高质量?因为提取特征的方式或者说维度有很多,要保证提取到的特征数据最具代表性、能够充分表达原数据的特点。现以‘石头剪刀布手势识别’为例子,来说明如何人工提取特征数据。我们知道,石头剪刀布3种手势之间手指的形状差异很大,手指和手指关节之间的直线距离差异很明显,那么我们可以将关节之间直线距离当作关键点的特征数据,并将其提取出来。

 使用提取到的特征数据(特征向量,Feature Vector),训练K-Means或者DBSCAN等机器学习分类算法模型,并将其泛用到其他新数据(新特征)。

 

关键点+深度学习方式

跟机器学习不同,深度学习的一大特点就是神经网络能够自动提取特征、自动寻找源数据内部的规律。至于它如何提取、或者根据什么原理去提取,很难解释。你只需要将源数据(关键点坐标)输入神经网络,直接可以输出对应结果,如果是前面提到的‘石头剪刀布手势识别’任务,网络可以输出三种手势分别对应的概率。我们可以看到,机器学习和深度学习两种方式的输入是不一样的,前者的输入是人工提取的特征数据(某些关节点之间的直线距离值集合),后者输入可以直接是关键点坐标值(关键点XY值集合)。正因为深度学习这种端到端的处理方式、省去了繁琐的特征工程,才能使得其大规模应用在复杂的非结构化数据上,比如CV中的CNN网络,它直接接收RGB格式图像作为输入,它处理的对象是像素值,而无需人工做过多的干预。关于这块详细参见之前的一篇文章。

我们用关键点数据训练最简单的神经网络,将训练得到的模型应用到真实数据上。本文主要介绍了肢体识别的几种方式,以及采用关键点检测时,如何处理关键点数据。同时提到了机器学习和深度学习不同的工作流程,以及各自的特点。有问题的朋友可以留言交流。

强烈推荐 | 阿里开源的这11个神级项目 - 苏三说技术 - 博客园

mikel阅读(807)

来源: 强烈推荐 | 阿里开源的这11个神级项目 – 苏三说技术 – 博客园

前言

最近趁着国庆节放假休息,特地整理了一下,阿里巴巴开源的10款神级项目。

这些开源项目中的绝大多数,我都在实际工作中用过,或者有同事用过。确实挺不错,挺有价值的,现在推荐给大家。

1. Druid

Druid自称是Java语言中最好的数据库连接池,它能够提供强大的监控和扩展功能。监控后台如下图所示:

Druid的主要优点如下:

  • 它能监控数据库访问性能。
  • 它提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。
  • 它提供了多种监测连接泄漏的手段。
  • 它提供了数据库密码加密的功能。
  • 它能打印SQL执行日志。

github地址: https://github.com/alibaba/druid

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/druid

配置maven依赖:

<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>${druid-version}</version>
</dependency>

2. fastjson

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

fastjson的主要优点如下:

  • 速度快,fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
  • 使用广泛,fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。
  • 使用简单,fastjson的API十分简洁。
//序列化
String text = JSON.toJSONString(obj); 
//反序列化
VO vo = JSON.parseObject("{...}", VO.class); 
  • 功能完备,支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。

github地址: https://github.com/alibaba/fastjson

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/fastjson

配置maven依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

3. Dubbo

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。

同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。它目前已交给Apache管理和维护。

架构图如下:

Dubbo的主要优点如下:

  • 基于透明接口的RPC
  • 智能负载均衡
  • 自动服务注册和发现
  • 高扩展性
  • 运行时流量路由
  • 可视化服务治理
  • 云原生友好

github地址: https://github.com/apache/dubbo

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/dubbo/

配置maven依赖:

<properties>
    <dubbo.version>3.0.3</dubbo.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <version>${dubbo.version}</version>
        <type>pom</type>
    </dependency>
</dependencies>

4. Rocketmq

Apache RocketMQ是一个分布式消息和流媒体平台,具有低延迟、高性能和可靠性、万亿级容量和灵活的可扩展性。

它提供了多种功能:

  • 消息传递模式,包括发布/订阅、请求/回复和流媒体
  • 金融级交易消息
  • 基于DLedger的内置容错和高可用配置选项
  • 多种跨语言客户端,如Java、C/C++、Python、Go
  • 可插拔传输协议,例如 TCP、SSL、AIO
  • 内置消息追踪能力,也支持opentracing
  • 多功能大数据和流媒体生态系统集成
  • 按时间或偏移量的消息追溯
  • 可靠的 FIFO 和同一队列中的严格有序消息传递
  • 高效的拉推式消费模式
  • 单个队列百万级消息累积能力
  • 多种消息传递协议,如 JMS 和 OpenMessaging
  • 灵活的分布式横向扩展部署架构
  • 闪电般的批量消息交换系统
  • 各种消息过滤机制,例如 SQL 和 Tag
  • 用于隔离测试和云隔离集群的 Docker 镜像
  • 用于配置、指标和监控的功能丰富的管理仪表板
  • 认证和授权
  • 免费的开源连接器,用于源和接收器

rocketmq后台管理界面:

github地址: https://github.com/apache/rocketmq

maven中央仓库: https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter

rocketmq包含:服务端和客户端,在我们的项目中主要关注客户端的代码即可。

配置maven依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.3.0</version>
</dependency>

5. Arthas

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 Debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 Debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从JVM内查找某个类的实例?

分析代码消耗时间:

Arthas支持JDK 6+,能够运行在多种操作系统上,比如:Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

github地址: https://alibaba.github.io/arthas/

maven中央仓库: https://mvnrepository.com/artifact/com.taobao.arthas/arthas-spring-boot-starter

在目标机器执行如下命令即可启动arthas:

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

6. Nacos

Nacos是一个易于使用的平台,专为动态服务发现和配置以及服务管理而设计。它可以帮助您轻松构建云原生应用程序和微服务平台。

服务是Nacos的一等公民。Nacos 支持几乎所有类型的服务,例如Dubbo/gRPC 服务、Spring Cloud RESTFul 服务或Kubernetes 服务。

Nacos 提供了四大功能。

  • 服务发现和服务健康检查。Nacos 使服务通过 DNS 或 HTTP 接口注册自己和发现其他服务变得简单。Nacos 还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。
  • 动态配置管理。动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos 无需在更新配置时重新部署应用程序和服务,这使得配置更改更加高效和敏捷。
  • 动态 DNS 服务。Nacos 支持加权路由,让您更容易在数据中心内的生产环境中实现中层负载均衡、灵活的路由策略、流量控制和简单的 DNS 解析服务。它可以帮助您轻松实现基于 DNS 的服务发现,并防止应用程序耦合到特定于供应商的服务发现 API。
  • 服务和元数据管理。Nacos 提供了一个易于使用的服务仪表板,帮助您管理您的服务元数据、配置、kubernetes DNS、服务健康和指标统计。

Nacos 地图:

Nacos 生态图:

github地址: https://github.com/alibaba/nacos

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery

7. easyexcel

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。

easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。

64M内存1分钟内读取75M(46W行25列)的Excel

github地址: https://github.com/alibaba/easyexcel

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba/easyexcel

配置maven依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.6</version>
</dependency>

8. Sentinel

随着分布式系统变得越来越流行,服务之间的可靠性变得比以往任何时候都更加重要。

Sentinel以“流量”为切入点,在流量控制、 流量整形、熔断、系统自适应保护等多个领域开展工作,保障微服务的可靠性和弹性。

Sentinel具有以下特点:

  • 丰富的适用场景:Sentinel在阿里巴巴得到了广泛的应用,几乎覆盖了近10年双11(11.11)购物节的所有核心场景,比如需要限制突发流量的“秒杀”满足系统容量、消息削峰填谷、下游不可靠业务断路、集群流量控制等。
  • 实时监控:Sentinel 还提供实时监控能力。可以实时查看单台机器的运行时信息,以及500个节点以下集群的运行时信息汇总。
  • 广泛的开源生态系统:Sentinel 提供与 Spring Cloud、Dubbo 和 gRPC 等常用框架和库的开箱即用集成。您只需将适配器依赖项添加到您的服务即可轻松使用 Sentinel。
  • 多语言支持:Sentinel 为 Java、Go和C++提供了本机支持。
  • 丰富的SPI扩展:Sentinel提供简单易用的SPI扩展接口,可以让您快速自定义逻辑,例如自定义规则管理、适配数据源等。

功能概述:

生态系统景观:

github地址: https://github.com/alibaba/Sentinel

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core

配置maven依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.2</version>
</dependency>

9. otter

阿里巴巴B2B公司,因为业务的特性,卖家主要集中在国内,买家主要集中在国外,所以衍生出了杭州和美国异地机房的需求,同时为了提升用户体验,整个机房的架构为双A,两边均可写,由此诞生了otter这样一个产品。

otter第一版本可追溯到04~05年,此次外部开源的版本为第4版,开发时间从2011年7月份一直持续到现在,目前阿里巴巴B2B内部的本地/异地机房的同步需求基本全上了otte4。

目前同步规模:

  • 同步数据量6亿
  • 文件同步1.5TB(2000w张图片)
  • 涉及200+个数据库实例之间的同步
  • 80+台机器的集群规模

otter能解决什么?

  • 异构库同步。 mysql -> mysql/oracle. (目前开源版本只支持mysql增量,目标库可以是mysql或者oracle,取决于canal的功能)
  • 单机房同步 (数据库之间RTT < 1ms)

    a. 数据库版本升级

    b. 数据表迁移

    c. 异步二级索引

  • 异地机房同步 (比如阿里巴巴国际站就是杭州和美国机房的数据库同步,RTT > 200ms,亮点)

    a. 机房容灾

  • 双向同步

    a. 避免回环算法 (通用的解决方案,支持大部分关系型数据库)

    b. 数据一致性算法 (保证双A机房模式下,数据保证最终一致性,亮点)

  • 文件同步
    站点镜像 (进行数据复制的同时,复制关联的图片,比如复制产品数据,同时复制产品图片).

工作原理图:

单机房复制示意图:

异地机房复制示意图:

github地址: https://github.com/alibaba/otter

maven中央仓库: https://mvnrepository.com/artifact/com.alibaba.otter/canal.client

10. P3C

P3C插件呈现了阿里巴巴 Java 编码指南,它整合了阿里巴巴集团技术团队多年来的最佳编程实践。由于我们鼓励重用和更好地理解彼此的程序,因此大量 Java 编程团队对跨项目的代码质量提出了苛刻的要求。

阿里巴巴过去见过很多编程问题。例如,有缺陷的数据库表结构和索引设计可能会导致软件架构缺陷和性能风险。另一个例子是混乱的代码结构难以维护。此外,未经身份验证的易受攻击的代码容易受到黑客的攻击。为了解决这些问题,我们为阿里巴巴的Java开发人员编写了这份文档。

更多信息请参考阿里巴巴Java编码指南:

  • 中文版:阿里巴巴Java开发手册
  • 英文版:Alibaba Java Coding Guidelines

该项目由3部分组成:

  • PMD 实现
  • IntelliJ IDEA 插件
  • Eclipse 插件

四十九条规则是基于PMD实现的,更多详细信息请参考P3C-PMD文档。IDE 插件(IDEA 和 Eclipse)中实现的四个规则如下:

  • [Mandatory]禁止使用已弃用的类或方法。
    注意:例如,应该使用 decode(String source, String encode) 而不是不推荐使用的方法 decode(String encodeStr)。一旦接口被弃用,接口提供者就有义务提供一个新的接口。同时,客户端程序员有义务检查它的新实现是什么。
  • [Mandatory]来自接口或抽象类的重写方法必须用 @Override 注释标记。反例:对于 getObject() 和 get0bject(),第一个是字母“O”,第二个是数字“0”。为了准确判断覆盖是否成功,需要一个@Override注解。同时,一旦抽象类中的方法签名发生变化,实现类将立即报告编译时错误。
  • [Mandatory] 静态字段或方法应直接通过其类名而不是其对应的对象名来引用。
  • [Mandatory] hashCode 和 equals 的用法应该遵循:
  1. 如果 equals 被覆盖,则覆盖 hashCode。
  2. 这两个方法必须为 Set 重写,因为它们用于确保不会在 Set 中插入重复的对象。
  3. 如果使用自定义对象作为 Map 的键,则必须覆盖这两个方法。注意:String 可以用作 Map 的键,因为这两个方法已经被重写。

使用p3c插件的效果:

最新版阿里巴巴Java开发手册下载地址:
https://github.com/alibaba/p3c/blob/master/Java开发手册(嵩山版).pdf

github地址:https://github.com/alibaba/p3c/tree/master/idea-plugin

11. Spring Cloud Alibaba

Spring Cloud Alibaba 为分布式应用开发提供一站式解决方案。它包含开发分布式应用程序所需的所有组件,使您可以轻松地使用 Spring Cloud 开发应用程序。

使用Spring Cloud Alibaba,您只需添加一些注解和少量配置,即可将Spring Cloud应用连接到阿里巴巴的分布式解决方案,并通过阿里巴巴中间件构建分布式应用系统。

主要功能如下:

  • 流量控制和服务降级:默认支持 HTTP 服务的流量控制。您还可以使用注释自定义流量控制和服务降级规则。规则可以动态更改。
  • 服务注册和发现:可以注册服务,客户端可以使用 Spring 管理的 bean,自动集成 Ribbon 来发现实例。
  • 分布式配置:支持分布式系统中的外化配置,配置变化时自动刷新。
  • 事件驱动:支持构建与共享消息系统连接的高度可扩展的事件驱动微服务。
  • 分布式事务:支持高性能、易用的分布式事务解决方案。
  • 阿里云对象存储:海量、安全、低成本、高可靠的云存储服务。支持随时随地在任何应用程序中存储和访问任何类型的数据。
  • 阿里云SchedulerX:精准、高可靠、高可用的定时作业调度服务,响应时间秒级。
  • 阿里云短信:覆盖全球的短信服务,阿里短信提供便捷、高效、智能的通讯能力,帮助企业快速联系客户。

主要包含如下组件:

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
  • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

github地址: https://github.com/alibaba/spring-cloud-alibaba

配置maven依赖:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。