I.背景

众所周知,Thrift是一个RPC的框架,其可用于不同语言之间的服务相互调用。比如最近接触到的一个运用环境:
*前端使用Node.Js重构了部分我们的老旧代码(前后端未分离的SpringBoot项目),我们后端使用zookeeper+Thrift为新的Node.Js前端项目提供基本的DAO层服务支持*
所以基于这个项目,我大概了解了一下Thrift,该文章则均以Java为基础语言。


II.如何入门

因为后端已经有一套服务注册和暴露机制,所以服务已经是RPC的形式,所以我们仅需要使用Thrift IDL来重写一遍我们需要暴露的方法即可,Thrift IDL有两个比较好的参考选择:

  • Thrift 类型说明可以参考官方文档: Thrift Type,简单来说,thrift基本支持所有的Java基本类型以及引用类型:bool(boolean)、byte(byte)、i16(short)、i32(int)、i64(long)、double(double)、string(String)、binary(byte[])以及一些常用容器和自建类型(Struct);
  • Thrift IDL的书写规范则可以参考Thrift: The Missing Guide,这个文档相较于官方文档有更多的例子可以参考。

III.TIPS

  • 很多参考和学习文档,都将servicesstruct放在一个.thrift文件中,这种方式将service和其所需的struct绑定在一起,会导致个别struct重复出现在多个.thrift文件中,导致大量的代码重复。所以,应该与Java的编码风格保持一致,采用POJO类(struct)+接口(services)的方案,利用include关键字,将struct引进services中使用
  • Thrift支持基本所有的Java基本类型,但是注意是基本类型,类似于Java中的Integer、Boolean、Long、Double等基本类型包装类是不支持的,或许你可以使用struct实现一个类似的包装类结构进行数据承载。
  • Thrift支持enum枚举类型,但是如果没有description的枚举类型也可以直接使用string来承接。
  • Thrift是通过序列化和反序列化来获取对应struct结构体的数据的,所以struct中的数据顺序一定要和java文件中的一致,否则可能会出现数据对应关系错乱或者数据丢失。
  • 在编写struct体时,要注意java对象父级,如果父级中含有变量,不要忘记其变量的书写,且顺序一定在前面。
  • 如非必要,java对象中的常量可以不出现在thrift idlstruct结构体中。
  • 时间Date我们固定使用timestamp时间戳的形式传递,即long型。

IV. 例子

我们现在有一个Account对象,需要将其变为thrift文件,Account的结构如下:

    public class Account extends BaseEntity implements SecurityUser{

        private static final long serialVersionUID = 1L;
        public static final String PASSWORD_TIME = "passwordTime";
    
        private String password;                            // 密码
        private Date createTime;                            // 创建时间
        private Date lastLoginTime;                         // 最后登录时间
        private int loginCount = 0;                         // 登录次数
        private boolean enabled = true;
        private Date passwordTime;                          //密码修改时间
        private boolean freeze;                             //账户是否被冻结
    
        //该账户的绑定信息,非持久化字段
        @Transient
        private Set<Binding> bindings = new HashSet();
    
        @Transient
        private String name;//保存登录时用的用户名,非持久化字段

        //省略getter和setter
  }

根据上述结构我们可以书写如下的Account.thrift:

/**
  * 账户信息
  */
struct Account{
    1: string password,    //密码
    2: i64 createTime,     //创建时间
    3: i64 lastLoginTime,  //最后登录时间
    4: i32 loginCount,     //登陆次数
    5: bool enabled=true,
    6: i64 passwordTime,   //密码修改时间
    7: bool freeze,        //账户是否被冻结
}  

但是经过测试前端调用接口获取到的Account信息,要么数据错位,要么数据丢失,问题出在哪里呢?这时,我们发现Account对象继承了BaseEntity,实现了SecurityUser,我们去查看一下继承的BaseEntity对象:

public abstract class BaseEntity extends IdEntity implements Cacheable, TypeAliases{
    
    private static final long serialVersionUID = 1L;
    
    private static final String CACHE_NAMESPACE = "entity" ;

    public BaseEntity() {
        super();
    }

    public BaseEntity(Long id) {
        super(id);
    }
    
    //省略下方代码
}

我们发现其中并没有非常量变量,但是BaseEntity又继承了IdEntity,所以我们得再去看一看IdEntity:

public abstract class IdEntity implements Serializable{

    private static final long serialVersionUID = -1L;

    /**
     * Hibernate JPA环境中使用@GenericGenerator注解生成主键
     */
    @Id
    @GeneratedValue(generator = "longIdGenerator")
    @GenericGenerator(name = "longIdGenerator", strategy = "net.qiyuesuo.framework.id.LongIdGenerator")
    protected Long id;
    
    public IdEntity() {
        super();
    }
    
    public IdEntity(Long id) {
        super();
        this.id = id;
    }

    //省略getter和setter
}

这时我们发现IdEntity中含有一个Id的变量,所以我们需要重构一下刚刚书写的Account.thrift

/**
  * 账户信息
  */
struct Account{
    1: i64 id,          //账户Id
    2: string password,  //密码
    3: i64 createTime,  //创建时间
    4: i64 lastLoginTime, //最后登录时间
    5: i32 loginCount, //登陆次数
    6: bool enabled,
    7: i64 passwordTime,  //密码修改时间
    8: bool freeze, //账户是否被冻结
}  

书写完Account.thrift后,我们需要写其相应的接口即service,查看Interface AccountService:

public interface AccountService {
    
    /**
     * 创建账户
     * @param account
     * @return 返回创建后的Account对象
     */
    Account create(Account account);
    
    /**
     * 更新账户信息
     * @param account
     * @return
     */
    boolean update(Account account);
    
    /**
     * 修改开放平台密码  传入的 密码是未加密的
     * @param username
     * @param newPassword
     * @return
     */
    boolean updatePasswd(String username, String newPassword);
    
    /**
     * 重置密码
     * @param username 用户名
     * @param newPassword 新密码
     */
    void resetPasswd(String username, String newPassword);

    /**
     * 验证用户名和密码是否匹配
     * @param username
    * @param password
     */
    boolean matches(String username,String password);
}

有很多方法,但是如果前端只需要用到校验用户名和密码的方法,那么我就只需要暴露创建账户的方法,即:

include "Account.thrift"

service AccountService{
    /*
     * 校验用户名和密码
     */
    bool matches(1: string username,2: string password),
}

这样我们就完成了一个关于用户名和密码的校验方法的thrift idl文档的书写,前端需要执行thrift的一条语句对文件进行编译,以node.js为例(具体可参考:Apache Thrift Tutorial

thrift --gen <language> <Thrift filename>

thrift -r --gen js:node Account.thrift
thrift -r --gen js:node AccountService.thrift

版权声明:本文为rekent原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/rekent/p/9722841.html