java连接hbase_HBase 工具 | hbasesdk 推出HQL功能
hbase-sdk
基于HBase Client的相关API开发而来的一款轻量级的HBase ORM框架。提供SQL查询功能,以类SQL的方式——HQL读写HBase数据。?
针对HBase 1.x和2.xAPI的不同之处,在其上做了一层统一的封装。
hbase-sdk分为spring-boot-starter-hbase和hbase-sdk-core两部分。
SpringBoot项目中引入spring-boot-starter-hbase,在普通的Java项目中则可以使用hbase-sdk-core。
hbase-sdk
是一款轻量级的ORM框架,封装了对HBase数据表的读写操作和对集群的运维管理,并针对HBase1.x的API和2.xAPI的差异,做了统一的定义, 提供更加方便的调用API。同时,HQL的功能也已上线,提供了类SQL读写数据的能力,这将大大降低HBase Client API的使用门槛。API文档地址:
https://weixiaotome.gitee.io/hbase-sdk/
如果觉得这个项目不错可以 star 支持或者打赏它。
功能特性
- [x] 消除不同版本API的差异,重新定义接口规范
- [x] 优良的ORM特性,数据查询结果集自动映射Java实体类
- [x] HQL,以类SQL的形式读写HBase的表中数据
- [x] 利用spring-boot-starter-hbase无缝与SpringBoot集成
- [x] HBatis,类似于myBatis,提供配置文件管理HQL的功能(努力规划中)
- [x] 熔断能力,提供API级别的主备集群自动切换功能,保障服务的高可用(努力规划中)
- [x] JDK8+
快速开始
hbase-sdk
的每个版本经过测试完成之后,都会编译打包至各个模块,最后发布到maven中央仓库之中,只是最新版本的代码有一定的延迟。如果你想在第一时间体验该项目, 可以选择在Gitee或Github中克隆源码,在本地编译并运行测试用例。
hbase-sdk
基于java8开发,如果你想自己编译或体验,请确保已经安装了Java8和maven3+。此外,如果你想在本地进行开发调试,建议在本地存在一个可连通的HBase环境。如果你想快速搭建一个HBase的开发环境,请参考:
https://www.jielongping.com/archives/dockerhbasetest
hbase-sdk
开发所用的工具为IDEA,所以也极力推荐导入项目到idea中。
1. 普通项目
Maven
配置:
创建一个基础的 Maven
工程,HBase SDK API开发时基于的HBase版本主要是1.4.3和2.1.0。
所以,如果你的HBase版本是1.x,可以使用如下依赖。
<dependency> <groupId>com.github.CCweixiaogroupId> <artifactId>hbase-sdk-core_1.4artifactId> <version>2.0.6version>dependency>
如果你的HBase版本是2.x,则可以使用如下依赖。
<dependency> <groupId>com.github.CCweixiaogroupId> <artifactId>hbase-sdk-core_2.1artifactId> <version>1.0.5version>dependency>
hbase-sdk
目前最新的版本是2.0.6
。你可以在maven仓库中搜索CCweixiao来获取hbase-sdk相关jar包的最新版本。
https://mvnrepository.com/artifact/com.github.CCweixiao
当然,如果你想重新编译,以适配你自己的HBase版本,也可以选择下载源码,修改项目根pom.xml文件中的hbase.version
,之后运行如下编译命令:
git clone https://github.com/CCweixiao/hbase-sdk.git orgit clone https://gitee.com/weixiaotome/hbase-sdk.gitcd hbase-sdkmvn clean install -Dmaven.test.skip=true
2. 项目结构
API核心类继承结构示意图:
3. 在SpringBoot项目中使用
Maven
配置:
创建一个基于Maven
的spring boot工程。
<dependency> <groupId>com.github.CCweixiaogroupId> <artifactId>spring-boot-starter-hbase_1.4artifactId> <version>2.0.6version>dependency>
or
<dependency> <groupId>com.github.CCweixiaogroupId> <artifactId>spring-boot-starter-hbase_2.1artifactId> <version>2.0.6version>dependency>
spring-boot-starter-hbase这个模块中已经包含了hbase-sdk-core。
4. 引入hbase-client的依赖
除了引入hbase-sdk
的相关依赖之外,你还需要引入hbase-client
的依赖,hbase-client
的版本目前建议为1.2.x
、1.4.x
、2.1.x
。hbase-client1.x和2.x的新旧API有所差异。未来,hbase-sdk
会持续完善该依赖的版本兼容。
<dependency> <groupId>org.apache.hbasegroupId> <artifactId>hbase-clientartifactId> <version>1.4.3version>dependency>
or
<dependency> <groupId>org.apache.hbasegroupId> <artifactId>hbase-clientartifactId> <version>2.1.0version>dependency>
5. 配置HBase数据库连接
普通项目
// 数据读写APIHBaseTemplate hBaseTemplate = new HBaseTemplate("node1", "2181");// 管理员APIHBaseAdminTemplate hBaseAdminTemplate = new HBaseAdminTemplate("node1", "2181");// HQL操作APIHBaseSqlTemplate hBaseSqlTemplate = new HBaseSqlTemplate("localhost", "2181");
spring boot项目
application.yaml
spring: data: hbase: quorum: node1,node2,node3 node-parent: /hbase zk-client-port: 2181 root-dir: /hbase client-properties: hbase.client.retries.number=3
@Autowired 依赖注入
@Servicepublic class UserService { @Autowired private HBaseTemplate hBaseTemplate; @Autowired private HBaseAdminTemplate hBaseAdminTemplate;}
集群管理
HBaseAdminTemplate封装了HBaseAdmin的常用操作,比如namespace的管理、表的管理、以及快照管理等等,后续这些API将会更加完善。
创建namespace
@Test public void testCreateNamespace() { String namespaceName = "LEO_NS";
NamespaceDesc namespaceDesc = new NamespaceDesc(); namespaceDesc.setNamespaceName(namespaceName); // 为namespace添加属性 namespaceDesc = namespaceDesc.addNamespaceProp("desc", "测试命名空间") .addNamespaceProp("createBy", "leo").addNamespaceProp("updateBy", "admin");
hBaseTemplate.createNamespace(namespaceDesc); }
创建表
@Test public void testCreateTable() { String tableName = "LEO_NS:USER";
TableDesc tableDesc = new TableDesc(); tableDesc.setTableName(tableName);
tableDesc = tableDesc.addProp("tag", "测试用户表").addProp("createUser", "leo");
FamilyDesc familyDesc1 = new FamilyDesc.Builder() .familyName("INFO") .replicationScope(1) .compressionType("NONE") .timeToLive(2147483647) .maxVersions(1).build();
FamilyDesc familyDesc2 = new FamilyDesc.Builder() .familyName("INFO2") .replicationScope(0) .compressionType("SNAPPY") .timeToLive(864000) .maxVersions(3).build();
tableDesc = tableDesc.addFamilyDesc(familyDesc1).addFamilyDesc(familyDesc2);
hBaseTemplate.createTable(tableDesc, false); }
更多操作
可以参考相关API文档或测试用例
数据读写
类似于Hibernate,你也可以使用hbase-sdk框架所提供的ORM特性,来实现对HBase的数据读写操作。
创建数据模型类
public class ModelEntity { private String createBy; private Long createTime;
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public Long getCreateTime() { return createTime; }
public void setCreateTime(Long createTime) { this.createTime = createTime; }}
@HBaseTable(schema = "TEST", name = "LEO_USER", uniqueFamily = "info1")public class UserEntity extends ModelEntity{ @HBaseRowKey private String userId;
private String username; private int age; private List addresses;private Map contactInfo;private Double pay;@HBaseColumn(name = "is_vip", family = "INFO2", toUpperCase = true)private boolean isVip;public String getUserId() {return userId; }public void setUserId(String userId) {this.userId = userId; }public String getUsername() {return username; }public void setUsername(String username) {this.username = username; }public int getAge() {return age; }public void setAge(int age) {this.age = age; }public boolean isVip() {return isVip; }public void setVip(boolean vip) { isVip = vip; }public List getAddresses() {return addresses; }public void setAddresses(List addresses) {this.addresses = addresses; }public Map getContactInfo() {return contactInfo; }public void setContactInfo(Map contactInfo) {this.contactInfo = contactInfo; }public Double getPay() {return pay; }public void setPay(Double pay) {this.pay = pay; }@Overridepublic String toString() {return "UserEntity{" +"userId='" + userId + '\'' +", username='" + username + '\'' +", age=" + age +", addresses=" + addresses +", contactInfo=" + contactInfo +", pay=" + pay +", isVip=" + isVip +'}'; }}
@HBaseTable(schema = "TEST", name = "LEO_USER", uniqueFamily = "info1")
HBaseTable注解用于定义HBase的表信息,schema用于定义该表的命名空间,如果不指定,默认是default, name用于定义该表的表名,如果不指定,表名则为类名的组合单词拆分加'_'拼接,如:UserEntity对应的表名为:user_entity。uniqueFamily用于定义如果所有的字段不特配置列簇名,则使用此处配置的列簇名。
@HBaseRowKey private String userId;
该注解表示userId字段为rowKey字段。
@HBaseColumn(name = "is_vip", family = "INFO2", toUpperCase = true) private boolean isVip; 该注解用于定义一个字段信息,name用于定义字段名,如果不指定,则默认使用字段名的组合单词拆分加'_'拼接,如:isVip,对应的字段名是:is_vip. family用于定义该字段属于INFO2列簇,toUpperCase表示字段名是否转大写,默认false,不做操作。
保存数据
@Test public void testSaveUser() { UserEntity userEntity = new UserEntity(); userEntity.setUserId("10001"); userEntity.setUsername("leo"); userEntity.setAge(18); userEntity.setVip(true); userEntity.setAddresses(Arrays.asList("北京", "上海")); userEntity.setCreateBy("admin"); userEntity.setCreateTime(System.currentTimeMillis());
Map contactInfo = new HashMap<>(2); contactInfo.put("email", "2326130720@qq.com"); contactInfo.put("phone", "18739577988"); contactInfo.put("address", "浦东新区"); userEntity.setContactInfo(contactInfo); userEntity.setPay(100000.0d);try { hBaseTemplate.save(userEntity); System.out.println("用户数据保存成功!"); } catch (Exception e) { e.printStackTrace(); } }
除此之外,保存数据时也可以不必构造数据模型类,而直接构造map数据模型。
@Test public void testToSave() { Map data = new HashMap<>(); data.put("info1:addresses", Arrays.asList("广州", "深圳")); data.put("info1:username", "leo"); data.put("info1:age", 18); data.put("INFO2:IS_VIP", true); data.put("info1:pay", 10000.1d); data.put("info1:create_by", "tom"); data.put("info1:create_time", System.currentTimeMillis()); Map contactInfo = new HashMap<>(2); contactInfo.put("email", "2326130720@qq.com"); contactInfo.put("phone", "18739577988"); contactInfo.put("address", "浦东新区"); data.put("info1:contact_info", contactInfo); hBaseTemplate.save("TEST:LEO_USER", "10002", data); System.out.println("用户数据保存成功!"); }
批量保存数据
@Test public void testToSaveBatch() { Map> data = new HashMap<>(); Map data1 = new HashMap<>(); data1.put("info1:username", "kangkang"); data1.put("info1:age", 18); data1.put("INFO2:IS_VIP", true); Map data2 = new HashMap<>(); data2.put("info1:username", "jane"); data2.put("info1:age", 18); data2.put("INFO2:IS_VIP", false); data.put("12003", data1); data.put("11004", data2); hBaseTemplate.saveBatch("TEST:LEO_USER", data); System.out.println("用户数据批量保存成功!"); }
根据RowKey查询
@Test public void testGet() { UserEntity userEntity = hBaseTemplate.getByRowKey("10001", UserEntity.class); final UserEntity userEntity1 = hBaseTemplate.getByRowKey("10002", UserEntity.class); System.out.println("用户数据获取成功!"); System.out.println(userEntity); }
@Test public void testGetToMap() { Map userInfo = hBaseTemplate.getByRowKey("TEST:LEO_USER", "10001"); System.out.println(Boolean.valueOf(userInfo.get("INFO2:IS_VIP").toString())); System.out.println(userInfo); }
scan查询
@Test public void testFind() { final List userEntities = hBaseTemplate.findAll(10, UserEntity.class); System.out.println(userEntities); System.out.println("用户数据批量查询"); }@Testpublic void testFindByPrefix() {final List userEntities = hBaseTemplate.findByPrefix("11", 10, UserEntity.class); System.out.println("用户数据批量查询"); }
删除数据
@Test public void testDeleteData() { hBaseTemplate.delete("TEST:LEO_USER", "12003"); hBaseTemplate.delete("TEST:LEO_USER", "11004", "INFO2"); hBaseTemplate.delete("TEST:LEO_USER", "10001", "info1", "addresses"); System.out.println("数据删除完成"); }
@Test public void testDeleteBatch() { hBaseTemplate.deleteBatch("TEST:LEO_USER", Arrays.asList("10001", "10002")); hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10003"), "INFO2"); hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10004"), "info1", "age", "username"); }
HQL
hbase-sdk
从2.0.6版本开始,开始提供HQL功能,一种以类SQL的方式读写HBase集群的数据,降低API的使用复杂度。HQL的操作依赖HBaseSqlTemplate
来完成, 因此使用之前,必须构造好HBaseSqlTemplate
的对象实例。
构造HBaseSqlTemplate的示例。
private HBaseSqlTemplate hBaseSqlTemplate;
@Before public void testInitHBaseSqlTemplate() { hBaseSqlTemplate = new HBaseSqlTemplate("localhost", "2181");
List hBaseColumnSchemas = createHBaseColumnSchemaList(); HBaseTableSchema hBaseTableSchema = new HBaseTableSchema(); hBaseTableSchema.setTableName("LEO_USER"); hBaseTableSchema.setDefaultFamily("g");//hBaseTableSchema.setRowKeyHandlerName("string"); HBaseTableConfig hBaseTableConfig = new DefaultHBaseTableConfig(hBaseTableSchema, hBaseColumnSchemas); hBaseSqlTemplate.setHBaseTableConfig(hBaseTableConfig); }public List createHBaseColumnSchemaList() { List hBaseColumnSchemas = new ArrayList<>(); HBaseColumnSchema hBaseColumnSchema1 = new HBaseColumnSchema(); hBaseColumnSchema1.setFamily("g"); hBaseColumnSchema1.setQualifier("id"); hBaseColumnSchema1.setTypeName("string"); HBaseColumnSchema hBaseColumnSchema2 = new HBaseColumnSchema(); hBaseColumnSchema2.setFamily("g"); hBaseColumnSchema2.setQualifier("name"); hBaseColumnSchema2.setTypeName("string"); HBaseColumnSchema hBaseColumnSchema3 = new HBaseColumnSchema(); hBaseColumnSchema3.setFamily("g"); hBaseColumnSchema3.setQualifier("age"); hBaseColumnSchema3.setTypeName("int"); HBaseColumnSchema hBaseColumnSchema4 = new HBaseColumnSchema(); hBaseColumnSchema4.setFamily("g"); hBaseColumnSchema4.setQualifier("address"); hBaseColumnSchema4.setTypeName("string"); hBaseColumnSchemas.add(hBaseColumnSchema1); hBaseColumnSchemas.add(hBaseColumnSchema2); hBaseColumnSchemas.add(hBaseColumnSchema3); hBaseColumnSchemas.add(hBaseColumnSchema4);return hBaseColumnSchemas; }
构造hBaseSqlTemplate示例需要先构造HBaseTableConfig,HBaseTableConfig的两个成员变量,
protected HBaseTableSchema hBaseTableSchema; protected List hBaseColumnSchemaList;
分别用来表的Schema信息和HBase表对应列的元数据信息。
针对HBase表列的数据类型转换,目前内置的实现有:
Boolean、Byte、Char、Date、Double、Float、Hex、Int、Long、Short、String
通过实现LiteralInterpreter
接口,你可以定义自己的列数据类型转换实现。
{ "tableName":"TEST:USER", "defaultFamily":"INFO", "columnSchema":[ { "family":"INFO", "qualifier":"name", "typeName":"string" }, { "family":"INFO2", "qualifier":"age", "typeName":"int" } ]}
通过实现相应的接口,你可以选择加载HBase表、列元数据信息的方式。如:类型myBatis在XML文件中加载。
HBaseSqlTemplate的实例准备好之后,就可以使用HQL来进行数据读写。
insert
insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10001', 'leo1' , '18', 'shanghai' ) where rowKey is stringkey ( 'a10002' ) ts is '1604160000000'
insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10002', 'leo2' , '17', 'beijing' ) where rowKey is stringkey ( 'a10002' )
@Test public void testInsertSql() { String sql = "insert into LEO_USER ( g:id , g:name , g:age , g:address ) values ( '10001', 'leo' , '18', 'shanghai' ) where rowKey is stringkey ( 'a10002' ) ts is '1604160000000'"; hBaseSqlTemplate.insert(sql); System.out.println("insert successfully!"); }
select
select ( g:id , g:name , g:age , g:address ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) maxversion is 2 startTS is '1604160000000' , endTS is '1604160000001' limit 1, 10
select * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo1' and age less '20' ) or ( id greater '10000' ) ) maxversion is 2 startTS is '1604160000000' , endTS is '1604160000001' limit 10
@Test public void testSelectSql() { String sql = "select ( g:id , g:name , g:age , g:address ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) maxversion is 2 startTS is '1604160000000' , endTS is '1604160000001' limit 10 "; sql = "select * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10002' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) maxversion is 2 startTS is '1604160000000' , endTS is '1604160000001' limit 10 ";
final List> listList = hBaseSqlTemplate.select(sql); listList.forEach(dataList -> { dataList.forEach(data -> { System.out.println(data.getRowKey()); System.out.println(data.getFamilyStr()); System.out.println(data.getQualifierStr()); System.out.println(data.getTsDate()); System.out.println("########################################"); }); }); }
delete
delete ( id , name ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'delete * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'delete * from LEO_USER where rowKey is stringkey ( 'a10002' ) ( name equal 'leo2' or age less '21' ) ts is '1604160000000'
@Test public void testDeleteSql(){ String sql = "delete ( id , name ) from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'"; sql = "delete * from LEO_USER where startKey is stringkey ( 'a10001' ) , endKey is stringkey ( 'a10003' ) ( ( name equal 'leo' and age less '12' ) or ( id greater '10000' ) ) ts is '1604160000000'"; sql = "delete * from LEO_USER where rowKey is stringkey ( 'a10002' ) ( name equal 'leo' or age less '21' ) ts is '1604160000000'";
hBaseSqlTemplate.delete(sql); }
特别鸣谢
HQL的语法设计以及antlr4的语法解析,有参考alibaba的开源项目 simplehbase
,在此特别感谢。simplehbase感觉是一个被遗弃的项目,针对的HBase版本是0。94, 已经有超过6年没有维护了。
hbase-sdk
在simplehbase的基础上进行重组和解耦,以兼容hbase-sdk
原有的框架设计,并便于以后的扩展。
hbase-sdk 目前的不足
非HQL的数据读写API还不丰富,特别是数据过滤的查询API。
HQL的antlr4解析功能不太完善,比如,目前HQL对中文要求不太好,同时,HQL对语法的要求比较严格,多一个空格少一个空格貌似都会引起语法错误。后续会针对这些缺点一一优化。
未来计划
- HBatis,类似于MyBatis的ORM框架,以XML管理SQL的方式维护集群数据的读写操作
- 集成Hystrix熔断框架,实现API层面的主备集群自动切换功能
- 还有更多
更新日志
v2.0.6 2020-11-29
- HQL功能上线
v2.0.5 2020-11-14
- 新增功能与代码优化
v2.0.3 2020-10-08
- 大量重构和优化
v1.0.5 2020-09-07
- 完善基础API的功能
- 完成ORM特性
- 模块拆分
- ......
java连接hbase_HBase 工具 | hbasesdk 推出HQL功能相关推荐
- java 连接mysql工具类_java连接Mysql数据库的工具类
一个封装好的链接Mysql数据库的工具类,可以方便的获取Connection对象关闭Statement.ResultSet.Statment对象等等 复制代码 代码如下: package myUtil ...
- java连接sftp工具类
本工具类支持远程连接sftp,上传下载文件 需要用到是jar是jsch-0.1.29.jar import java.io.BufferedReader;import java.io.File;imp ...
- java连接ftp工具类
这里使用了org.apache.commons.net.ftp这个类库,仅仅是对这个类库稍微封装了一下方便使用,这里写了一个工具类,大家可以参考一下. 介绍一个 ftp客户端工具:iis7服务器管理工 ...
- java 连接SSH工具操作服务器 (构建者模式+Util类) 分享
需求 因为需要以java 远程操作服务器, 比如进行文件下载/上传操作, 或者执行一些服务器常用命令ls cat grep 等等. 调研发现比较好用的SSH 工具有: ganymed-ssh2 jsc ...
- java连接hbase_HBase实战 | 05405.15.0Spark2使用HBaseSpark访问HBase
转载自微信公众号Hadoop实操 温馨提示:如果使用电脑查看图片不清晰,可以使用手机打开文章单击文中的图片放大查看高清原图. Fayson的github: https://github.com/fay ...
- java获取excle表格对象_Java使用excel工具类导出对象功能示例
本文实例讲述了Java使用excel工具类导出对象功能.分享给大家供大家参考,具体如下: package com.gcloud.common; import org.apache.poi.ss.use ...
- Java连接MySql数据库实现增删改查功能
一 不用工具类,简单实现连接数据库并查询 package com.liu;import java.sql.*;public class JDBCTest {public static void m ...
- Java短连接生成工具-思路
Java短连接生成工具-思路-这里只是模拟一下 package com.csrs.trans.shorturl;import java.util.HashMap; import java.util.M ...
- IDEA2019 Java连接PostgreSQL数据库实现基础功能增删改查
IDEA2019 Java连接PostgreSQL数据库实现基础功能增删改查 注意: 每个方法对应单个java类,可以自行进行整理汇总到一个类中 一.Java通过JDBC连接到PostgreSQL数据 ...
最新文章
- java多线程机制_Java的多线程机制
- 网络营销专员浅析如何判断网络营销中网站优化效果几何?
- python代码案例详解-Python编程:案例详解输出函数print
- Laravel 实践之路: 数据库迁移与数据填充
- 1152 Google Recruitment (20 分)【难度: 简单 / 知识点: 模拟】
- 请写php合并数组,合并PHP数组
- 1965 - 2019 年最流行的编程语言变化
- vb 6.0 获取重定向的url_接口测试:A07_HttpRunner重定向_04_解决方案
- 常见的6种JavaScript设计模式
- 异步与并行~ReaderWriterLockSlim实现的共享锁和互斥锁
- git wechat.class.php,wechat-php-sdk/qywechat.class.php at master · gitye/wechat-php-sdk · GitHub
- Vue使用Element-ui按需引入大坑
- TeamCity : .NET Core 插件
- 用js来实现那些数据结构01(数组篇01-数组的增删)
- python列表推导式生成随机数_Python:列表推导式/生成器推导式
- C++ 日期 时间
- 用QLabel实现抽奖
- 英文分词的算法和原理
- Java使用jfreechart画饼图_JFreeChart饼图
- 微信小程序如何跳转到tabbar页面-陆大湿