ToB企服应用市场:ToB评测及商务社交产业平台

标题: 大数据之Hive基础 [打印本页]

作者: 欢乐狗    时间: 2024-11-29 14:11
标题: 大数据之Hive基础
B站尚硅谷hive学习视频整理,用于自查

  
1.什么是hive

Hive 是基于 Hadoop 的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类 SQL 查询功能。本质是将HQL转化为MapReduce
2.Hive优缺点


3.hive和数据库比较

1)查询语言
由于 SQL 被广泛的应用在数据仓库中,因此,专门针对 Hive 的特性设计了类 SQL 的查询语言 HQL。熟悉 SQL 开发的开发者可以很方便的使用 Hive 举行开发。
2)数据更新
由于 Hive 是针对数据仓库应用设计的,而数据仓库的内容是读多写少的。因此,Hive 中不建议对数据的改写,所有的数据都是在加载的时候确定好的(一次写入,多次查询)。而数据库中的数据通常是必要常常举行修改的,因此可以使用 INSERT INTO … VALUES 添加数据,使用 UPDATE … SET 修改数据。
3)执行延迟
Hive 在查询数据的时候,由于没有索引,必要扫描整个表,因此延迟较高。别的一个导致 Hive 执行延迟高的因素是 MapReduce 框架。由于 MapReduce 本身具有较高的延迟,因此在利用 MapReduce 执行 Hive 查询时,也会有较高的延迟。相对的,数据库的执行延迟较低。当然,这个低是有条件的,即数据规模较小,当数据规模大到凌驾数据库的处理本领的时候,Hive 的并行盘算显然能体现出上风。
4)数据规模
由于 Hive 创建在集群上并可以利用 MapReduce 举行并行盘算,因此可以支持很大规模的数据;对应的,数据库可以支持的数据规模较小。
4.hive常用交互下令

1)“-e”不进入 hive 的交互窗口执行 sql 语句
  1. hive -e "select id from student;"
复制代码
2)“-f”执行脚本中 sql 语句并将结果写入txt文件中
  1. bin/hive -f /opt/module/hive/datas/hivef.sql > /opt/module/datas/hive_result.txt
复制代码
3)退出
  1. exit;
复制代码
4)在 hive cli 下令窗口中如何检察 hdfs 文件体系
  1. hive(default)>dfs -ls /;
复制代码
5)检察在 hive 中输入的所有历史下令

  1. cat .hivehistory
复制代码
5.hive数据类型


   STRUCT和 c 语言中的 struct 类似,都可以通过“点”符号访问元素内容。比方,如果某个列的数据类型是 struct<first:STRING, last:STRING>,那么第 1 个元素可以通过字段struct.first 来引用。
  6.将文件数据load到hive表中

(1)假设某表有如下一行,我们用 JSON 格式来表示其数据结构。在 Hive 下访问的格式为
  1. {
  2. "name": "songsong",
  3. "friends": ["bingbing" , "lili"] , //列表 Array,
  4. "children": { //键值 Map,
  5.      "xiao song": 18 ,
  6.      "xiaoxiao song": 19
  7. }
  8. "address": { //结构 Struct,
  9.      "street": "hui long guan",
  10.      "city": "beijing"
  11. }
  12. }
复制代码
2)基于上述数据结构,我们在 Hive 里创建对应的表,并导入数据。创建当地测试文件 test.txt
  1. {"name": "songsong", "friends": ["bingbing" , "lili"] , "children": { "xiao song": 18 ,"xiaoxiao song": 19}"address": {"street": "hui long guan","city": "beijing"}}
复制代码
3)测试表 test
  1. create table test(
  2. name string,
  3. friends array<string>,
  4. children map<string, int>,
  5. address struct<street:string, city:string>
  6. )
  7. row format serde 'org.apache.hadoop.hive.serde2.JsonSerDe'
  8. location '/user/hive/warehouse/test';
复制代码
4)导入文本数据到测试表
  1. load data local inpath '/opt/module/hive/datas/test.txt' into table test;
复制代码
hdfs上的文件被load后会被删除
5)访问三种集合列里的数据,以下分别是 ARRAY,MAP,STRUCT 的访问方式
  1. select friends[1],children['xiao song'],address.city from test where name="songsong";
  2. OK
  3. _c0 _c1 city
  4. lili 18 beijing
  5. Time taken: 0.076 seconds, Fetched: 1 row(s)
复制代码
7.类型转化


8.DDL


  1. --增
  2. CREATE DATABASE [IF NOT EXISTS] database_name
  3. [COMMENT database_comment]
  4. [LOCATION hdfs_path] --指定存放位置
  5. [WITH DBPROPERTIES (property_name=property_value, ...)];
  6. --删
  7. drop database if exists db_hive2;
  8. drop database db_hive cascade;  --如果数据库不为空,可以采用 cascade 命令,强制删除
  9. --改
  10. alter database db_hive set dbproperties('createtime'='20170830');
  11. --查
  12. desc database extended db_hive;  --extended 查看详细信息
复制代码

  1. --增
  2. CREATE [TEMPORARY][EXTERNAL] TABLE [IF NOT EXISTS] table_name  --EXTERNAL外部表
  3. [(col_name data_type [COMMENT col_comment], ...)]
  4. [COMMENT table_comment]
  5. [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]  --分区
  6. [CLUSTERED BY (col_name, col_name, ...)[SORTED BY (col_name [ASC|DESC]**-, ...)] INTO num_buckets BUCKETS]   --分桶
  7. [ROW FORMAT row_format]
  8. [STORED AS file_format]
  9. [LOCATION hdfs_path]
  10. [TBLPROPERTIES (property_name=property_value, ...)]
  11. [AS select_statement]
  12. [like table]
复制代码
  ROW FORMAT指定SerDe,序列化和反序列化
语法一:
ROW FORMAT DELIMITED
[FIELDS TERMINATED BY char] --列分隔符
[COLLECTION ITEMS TERMINATED BY char] --map、struct和array中每个元素之间的分隔符
[MAP KEYS TERMINATED BY char] --map中key与value的分隔符
[LINES TERMINATED BY char] --行分隔符
[NULL DEFINED AS char]
语法二:
ROW FORMAT SERDE serde_name [WITH SERDEPROPERTIES (propertyname=property_value,…)]
    STORED AS 指定存储文件类型,常用的存储文件类型:SEQUENCEFILE(二进制序列文件)、TEXTFILE(文本,默认值=)、ORC、PARQUET。
AS:后跟查询语句,根据查询结果创建表,会复制数据,不答应创建外部表
LIKE 答应用户复制现有的表结构,但是不复制数据,答应创建外部表
  1. --查
  2. show create table table_name;
  3. desc [formatted|extended] table_name;
  4. --删(如果是删除外部表,hdfs 中的数据还在,但是 metadata 中 dept 的元数据已被删除)
  5. drop table table_name;  
  6. truncate table table_name;
  7. --修改内部表 student2 为外部表
  8. alter table student2 set tblproperties('EXTERNAL'='TRUE');
  9. --更新列
  10. ALTER TABLE table_name CHANGE [COLUMN] col_old_name col_new_name column_type [COMMENT col_comment] [FIRST|AFTER column_name]
  11. --增加和替换列(ADD 是代表新增一字段,字段位置在所有列后面(partition 列前),REPLACE 则是表示替换表中所有字段。)
  12. ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name data_type [COMMENT col_comment], ...)
复制代码
  alter 后HDFS数据文件并不会更改,必要重新load文件数据才能使用。
在alter时会自动校验数据类型可否转换,比如string转为int会报错,可以设置set hive.metastore.disallow.incompatible.col.type.changes=false;关闭校验
  9.DML


  1. --向表中装载数据
  2. load data [local] inpath '数据的 path' [overwrite] into table student [partition (partcol1=val1,…)];
  3. --insert(overwrite覆盖 into追加)不支持插入部分字段
  4. insert overwrite/into table student_par select id, name from student where month='201709';
  5. --查询语句中创建表并加载数据
  6. create table if not exists student3 as select id, name from student;
  7. --创建表时通过 Location 指定加载数据路径
  8. create external table if not exists student5( id int, name string)
  9. row format delimited fields terminated by '\t'
  10. location '/student';
  11. -- Import 数据到指定 Hive 表中
  12. import table student2 from '/user/hive/warehouse/export/student';
复制代码

  1. --将查询的结果导出
  2. insert overwrite local directory '/opt/module/hive/data/export/student1' --local表示导出至本地,如无,表示导出到hdfs
  3. ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'--格式化,可省略
  4. select * from student;
  5. -- Hadoop 命令导出到本地
  6. dfs -get /user/hive/warehouse/student/student.txt/opt/module/data/export/student3.txt;
  7. --Hive Shell 命令导出(hive -f/-e 执行语句或者脚本 > file)
  8. bin/hive -e 'select * from default.student;' >/opt/module/hive/data/export/student4.txt;
  9. --Export 导出到 HDFS 上(export 和 import 主要用于两个 Hadoop 平台集群之间 Hive 表迁移)
  10. export table default.student to '/user/hive/warehouse/export/student';
  11. --清除(Truncate 只能删除管理表,不能删除外部表中数据)
  12. truncate table table_name
复制代码

10.分区

建表:
  1. create table dept_partition(
  2. deptno int, dname string, loc string
  3. )
  4. partitioned by (day string)
  5. row format delimited fields terminated by '\t';
复制代码
注意:分区字段不能是表中已经存在的数据,可以将分区字段看作表的伪列。
加载数据
  1. load data local inpath '/opt/module/hive/datas/dept_20200401.log' into table dept_partition partition(day='20200401');
复制代码
分区表加载数据时,必须指定分区
创建分区
  1. alter table dept_partition add partition(day='20200404');
  2. alter table dept_partition add partition(day='20200405') partition(day='20200406');
复制代码
删除分区(注意逗号
  1. alter table dept_partition drop partition (day='20200406');
  2. alter table dept_partition drop partition (day='20200404'), partition(day='20200405');
复制代码
检察分区
  1. show partitions dept_partition;
复制代码
创建二级分区表
  1. create table dept_partition2(
  2. deptno int, dname string, loc string)
  3. partitioned by (day string, hour string)
  4. row format delimited fields terminated by '\t';
复制代码
11.数据上传到分区表的方式

方法一:上传后同步
1)上传文件
  1. hive (default)> dfs -mkdir -p
  2. /user/hive/warehouse/mydb.db/dept_partition2/day=20200401/hour=13;
  3. hive (default)> dfs -put /opt/module/datas/dept_20200401.log
  4. /user/hive/warehouse/mydb.db/dept_partition2/day=20200401/hour=13;
复制代码
此时查询不到刚上传的数据
2)执行修复下令
  1. hive> msck repair table dept_partition2;
复制代码
此时可以查询到数据
方法二:上传数据后添加分区
1)上传文件
2)执行添加分区
  1. hive (default)> alter table dept_partition2 add partition(day='201709',hour='14');
复制代码
方法三:创建文件夹后 load 数据到分区
1)创建目录
  1. hive (default)> dfs -mkdir -p /user/hive/warehouse/mydb.db/dept_partition2/day=20200401/hour=15;
复制代码
2)上传数据
  1. hive (default)> load data local inpath '/opt/module/hive/datas/dept_20200401.log' into table dept_partition2 partition(day='20200401',hour='15');
复制代码
12.严格模式和非严格模式

参数设置
  1. --开启非严格模式
  2. hive.exec.dynamic.partition.mode=nonstrict
  3. --开启严格模式
  4. hive.exec.dynamic.partition.mode=strict
复制代码

  1. --建表
  2. create table dept_partition_dy(id int, name string) partitioned by (loc int,dt timestamp) row format delimited fields terminated by '\t';
  3. --非严格模式
  4. set hive.exec.dynamic.partition.mode=nonstrict;
  5. hive (default)> insert into table dept_partition_dy partition(loc) select deptno, dname, loc, dt from dept;
  6. --严格模式
  7. set hive.exec.dynamic.partition.mode=strict;
  8. hive (default)> INSERT OVERWRITE TABLE dept_partition_dy PARTITION(dt='2024-11-19', loc)
  9. SELECT deptno, dname, loc from dept;
  10. --在这里,loc分区将由SELECT子句(即loc)的最后一列动态创建。
  11. --而dt分区是手动指定写死的。
复制代码
13.动态分区

动态分区是指向分区表insert时,被写入的分区不由用户指定,而是由每行数据的最后一个字段的值来动态决定。
开启动态分区参数设置
  1. --开启动态分区功能(默认 true,开启)
  2. hive.exec.dynamic.partition=true
  3. --设置为非严格模式
  4. hive.exec.dynamic.partition.mode=nonstrict
  5. --在所有执行 MR 的节点上,最大一共可以创建多少个动态分区。默认 1000
  6. hive.exec.max.dynamic.partitions=1000
  7. --在每个执行 MR 的节点上,最大可以创建多少个动态分区。该参数需要根据实际的数据来设定。比如:源数据中包含了一年的数据,即 day 字段有 365 个值,那么该参数就需要设置成大于 365,如果使用默认值 100,则会报错。
  8. hive.exec.max.dynamic.partitions.pernode=100
  9. --整个 MR Job 中,最大可以创建多少个 HDFS 文件。默认 100000
  10. hive.exec.max.created.files=100000
  11. --当有空分区生成时,是否抛出异常。一般不需要设置。默认 false
  12. hive.error.on.empty.partition=false
复制代码
14.分桶

对于一张表或者分区,Hive 可以进一步构造成桶,也就是更为细粒度的数据范围分别。
分桶是将数据集分解成更轻易管理的多少部分的另一个技术。
分区针对的是数据的存储路径;分桶针对的是数据文件。
建表
  1. create table stu_buck(id int, name string)
  2. clustered by(id)
  3. into 4 buckets
  4. row format delimited fields terminated by '\t';
复制代码
分桶规则:
Hive 的分桶接纳对分桶字段的值举行哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中
分桶表操作必要注意的事项:
(1)reduce 的个数设置为-1,让 Job 自行决定必要用多少个 reduce 或者将 reduce 的个
数设置为大于便是分桶表的桶数
(2)从 hdfs 中 load 数据到分桶表中,避免当地文件找不到题目
(3)不要使用当地模式
15.抽样查询

对于非常大的数据集,有时用户必要使用的是一个具有代表性的查询结果而不是全部结果。Hive 可以通过对表举行抽样来满足这个需求。
语法: TABLESAMPLE(BUCKET x OUT OF y)
查询表 stu_buck 中的数据。
  1. hive (default)> select * from stu_buck tablesample(bucket 1 out of 4 on id);
复制代码
注意:x 的值必须小于便是 y 的值,否则
  1. FAILED: SemanticException [Error 10061]: Numerator should not be bigger than denominator in sample clause for table stu_buck
复制代码
16.常用函数

1)检察体系自带的函数hive> show functions;
2)显示自带的函数的用法hive> desc function upper;
3)具体显示自带的函数的用法hive> desc function extended upper;
4)空字段赋值,如果 value 为 NULL,则 NVL 函数返回default_value 的值,否则返回 value 的值,如果两个参数都为 NULL ,则返回 NULL。NVL( value,default_value)
5)CASE WHEN THEN ELSE END
6)字符串拼接:CONCAT(string A/col, string B/col…)
17.行转列


举例

需求
把星座和血型一样的人归类到一起。结果如下:

sql
  1. SELECT
  2. t1.c_b,
  3. CONCAT_WS("|",collect_set(t1.name))
  4. FROM (
  5. SELECT
  6. NAME,
  7. CONCAT_WS(',',constellation,blood_type) c_b
  8. FROM person_info
  9. )t1
  10. GROUP BY t1.c_b
复制代码
18. 列转行


举例

需求

  1. SELECT
  2. movie,
  3. category_name
  4. FROM
  5. movie_info
  6. lateral VIEW
  7. explode(split(category,",")) movie_info_tmp AS category_name;
复制代码
19.窗口函数

1)OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。
2)CURRENT ROW:当前行
3)n PRECEDING:往前 n 行数据
4)n FOLLOWING:今后 n 行数据
5)UNBOUNDED:起点,
UNBOUNDED PRECEDING 表示从前面的起点,
UNBOUNDED FOLLOWING 表示到后面的尽头
6)LAG(col,n,default_val):往前第 n 行数据
7)LEAD(col,n, default_val):今后第 n 行数据
8)NTILE(n):把有序窗口的行分发到指定命据的组中,各个组有编号,编号从 1 开始,对于每一行,NTILE 返回此行所属的组的编号。注意:n 必须为 int 类型。
9)RANK() 排序相同时会重复,总数不会变
10)DENSE_RANK() 排序相同时会重复,总数会减少
11)ROW_NUMBER() 会根据顺序盘算

  1. --(1)查询在 2017 年 4 月份购买过的顾客及总人数
  2. select name,count(*) over () from business where substring(orderdate,1,7) = '2017-04' group by name;
  3. --(2)查询顾客的购买明细及月购买总额
  4. select name,orderdate,sum(cost) over(partition by substring(1,7),name) from business;
  5. --(3)上述的场景, 将每个顾客的 cost 按照日期进行累加
  6. select name,orderdate,sum(cost) over(partition by substring(1,7),name order by orderdate) from business;
  7. --(4)查询每个顾客上次的购买时间
  8. select name,orderdate,lag(orderdate,1,'1900-01-01') over(partition by name order by orderdate) from business;
  9. --(5)查询前 20%时间的订单信息
  10. select * from (select *,row number() over(order by orderdate) rn from business) where rn<=3;
复制代码
20.自界说函数UDF

分类:
1)UDF 一进一出
2)UDAF 聚集函数,多进一出(类似于count,max)
3)UDTF 一进多出(如lateral view explode())
步骤:
1)导入依赖
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.apache.hive</groupId>
  4.         <artifactId>hive-exec</artifactId>
  5.         <version>3.1.2</version>
  6.     </dependency>
  7. </dependencies>
复制代码
2)创建函数类
3)打成 jar 包上传到服务器/opt/module/data/myudf.jar
4)将 jar 包添加到 hive 的 classpath
  1. hive (default)> add jar /opt/module/data/myudf.jar;
复制代码
5)创建临时函数与开发好的 java class 关联
  1. hive (default)> create temporary function my_len as "com.atguigu.hive.MyStringLength";
复制代码
6)即可在 hql 中使用自界说的函数
  1. hive (default)> select ename,my_len(ename) ename_len from emp;
复制代码
21.列式存储和行式存储


如图所示左边为逻辑表,右边第一个为行式存储,第二个为列式存储。
1)行存储的特点
查询满足条件的一整行数据的时候,列存储则必要去每个聚集的字段找到对应的每个列的值,行存储只必要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。
2)列存储的特点
由于每个字段的数据聚集存储,在查询只必要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法。
21.hive文件格式

Hive 支持的存储数据的格式主要有:TEXTFILE 、SEQUENCEFILE、ORC、PARQUET。
TEXTFILE 和 SEQUENCEFILE 的存储格式都是基于行存储的;
ORC 和 PARQUET 是基于列式存储的。

1)建表
  1. create table log_orc(
  2. track_time string,
  3. url string,
  4. session_id string,
  5. referer string,
  6. ip string,
  7. end_user_id string,
  8. city_id string
  9. )
  10. stored as orc
  11. tblproperties("orc.compress"="NONE"); -- 设置 orc 存储不使用压缩
复制代码
2)desc formatted log_orc;

SerDe:以ORC格式序列化和反序列化
InputFormat:以ORC格式输入
OutputFormat:以ORC格式输出
3)向表中加载数据
  1. hive (default)> insert into table log_orc select * from log_text;
复制代码
不能通过load data的方式加载数据,无法剖析
4)检察表中数据大小
  1. hive (default)> dfs -du -h /user/hive/warehouse/log_orc/ ;
复制代码
5)ORC文件格式支持的参数如下:
参数默认值说明orc.compressZLIB压缩格式,可选项:NONE、ZLIB,、SNAPPYorc.compress.size262,144每个压缩块的大小(ORC文件是分块压缩的)orc.stripe.size67,108,864每个stripe的大小orc.row.index.stride10,000索引步长(每隔多少行数据建一条索引)
是Hadoop生态中的一个通用的文件格式。以二进制方式存储,因此Parquet格式文件是自剖析的。

(1)行组(Row Group):每一个行组包罗一定的行数,在一个 HDFS 文件中至少存储一个行组,类似于 orc 的 stripe 的概念。
(2)列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。一个列块中的值都是相同类型的,差别的列块可能使用差别的算法举行压缩。
(3)页(Page):每一个列块分别为多个页,一个页是最小的编码的单位,在同一个列块的差别页可能使用差别的编码方式。
通常环境下,在存储 Parquet 数据的时候会按照 Block 大小设置行组的大小,由于一般环境下每一个 Mapper 使命处理数据的最小单位是一个 Block,这样可以把每一个行组由一个 Mapper 使命处理,增大使命执行并行度。
1)建表
  1. create table parquet_table
  2. (column_speces)
  3. stored as parquet
  4. tblproperties (property_name=property_value,...);
复制代码
2)加载数据方式与 orc相同。
3)支持的参数如下:
参数默认值说明parquet.compressionuncompressed压缩格式,可选项:uncompressed,snappy,gzip,lzo,brotli,lz4parquet.block.size134217728行组大小,通常与HDFS块大小保持一致parquet.page.size1048576页大小 22.压缩

在Hive表中和盘算过程中,保持数据的压缩,会提高磁盘空间的有用利用和查询性能。
为了支持多种压缩/解压缩算法,Hadoop 引入了编码/解码器,如下表所示:
压缩格式对应的编码/解码器DEFLATEorg.apache.hadoop.io.compress.DefaultCodecgziporg.apache.hadoop.io.compress.GzipCodecbzip2org.apache.hadoop.io.compress.BZip2CodecLZOcom.hadoop.compression.lzo.LzopCodecSnappyorg.apache.hadoop.io.compress.SnappyCodec bzip2和LZO可切片,DEFLATE,Gzip,Snappy不可切片
压缩速度:Snappy>LZO>Gzip>bzip2
解压速度:Snappy>LZO>Gzip>bzip2
22.1 建表时举行压缩

差别表类型的声明压缩方式是差别的。

多数环境下,无需在建表语句做出声明。直接将压缩后的文件导入到该表即可,Hive在查询表中数据时,可自动辨认其压缩格式,举行解压。必要注意的是,在执行往表中导入数据的SQL语句时,用户需设置以下参数,来保证写入表中的数据是被压缩的。
  1. --SQL语句的最终输出结果是否压缩
  2. set hive.exec.compress.output=true;
  3. --输出结果的压缩格式(以下示例为snappy)
  4. set mapreduce.output.fileoutputformat.compress.codec =org.apache.hadoop.io.compress.SnappyCodec;
复制代码
2)ORC
  1. create table orc_table
  2. (column_specs)
  3. stored as orc
  4. tblproperties ("orc.compress"="snappy");
复制代码
3)Parquet
  1. create table orc_table
  2. (column_specs)
  3. stored as parquet
  4. tblproperties ("parquet.compression"="snappy");
复制代码
22.2 盘算过程中压缩

1)单个MR的中间结果举行压缩
单个MR的中间结果是指Mapper输出的数据,对其举行压缩可降低shuffle阶段的网络IO,可通过以下参数举行配置:
  1. --开启MapReduce中间数据压缩功能
  2. set mapreduce.map.output.compress=true;
  3. --设置MapReduce中间数据数据的压缩方式(以下示例为snappy)
  4. set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;
复制代码
2)单条SQL语句的中间结果举行压缩
单条SQL语句的中间结果是指,两个MR(一条SQL语句可能必要通过MR举行盘算)之间的临时数据,可通过以下参数举行配置:
  1. --是否对两个MR之间的临时数据进行压缩
  2. set hive.exec.compress.intermediate=true;
  3. --压缩格式(以下示例为snappy)
  4. set hive.intermediate.compression.codec= org.apache.hadoop.io.compress.SnappyCodec;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4