过滤器request参数中payload丢失问题

Java Spring 过滤器 request 参数中 payload 丢失问题

省流版:

org.springframework.boot:spring-boot:2.1.8.RELEASE && org.springframework:spring-web:5.1.9.RELEASE​版本中的org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal​方法读取HttpServletRequest.InputStream​流导致后续过滤器和拦截器无法再次读取InputStream​。

起因:

最近有一个需要在前端对接口数据进行加密,再后端接口进行解密的需求,所以打算用rsa,前端请求数据公钥加密后,后端在拦截器中解密,而当前端请求到达后端自定义拦截器处时,读取出InputStream​为空,只能通过request.getParameter​取出对应的参数值,但是无法取出body​中的数据,也就是即无法获取json​数据

排查过程:

  1. (此步方向错误)首先,对上一级的过滤器进行排查,初步怀疑是上一级过滤器中的request​包装类代码逻辑错误,在包装过程中没有正确存储InputStream​所以搭建了一个最小环境测试包装类(与发生问题的环境版本不同),发现在其他环境中包装类能正常重读InputStream​数据。
  2. 再者,对项目中其他的过滤器进行屏蔽,发现并无效果,依然无法读取InputStream​数据。
  3. 通过IDEA Debugger Frames​逐个方法反推,逐步定位发现如果发起的是POST请求(GET请求也无法传递body​)在org.springframework.web.filter.OncePerRequestFilter​的子类中org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal​方法执行了request.getParameter(this.methodParam);​导致InputStream​数据丢失。

解决方案:

  1. 屏蔽HiddenHttpMethodFilter​过滤器,使其不能读取InputStream​,但是可能存在一些安全性的问题

       a. 屏蔽方法有两种,第一种是通过代码配置,参考链接、参考代码如下。
       

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class FilterConfig {
    @Bean
    public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
    }
    }

       b. 通过配置关闭,在Spring boot​2.1.0版本后可以通过配置关闭该过滤器,在2.2​版本后默认关闭,屏蔽配置:​spring.mvc.hiddenmethod.filter.enabled: false​。

  2. 通过转换为ServletServerHttpRequest​使用getBody​方法获取body​数据,代码如下。

    1
    new String(StreamUtils.copyToByteArray(new ServletServerHttpRequest((HttpServletRequest) servletRequest).getBody()))
  3. 重新实现HiddenHttpMethodFilter​中的doFilterInternal​方法,在读取之前将request​进行包装(未实践)

  4. HiddenHttpMethodFilter​调用之前将request​进行包装(未实践)

参考:

  1. HiddenHttpMethodFilter consumes input stream of POST request
  2. ServletServerHttpRequest.getBody
  3. Disable auto-configuration of HiddenHttpMethodFilter by default
  4. Why is HttpServletRequest inputstream empty?

Mysql 系数据库备份

使用 mysqldump 备份

1
2
3
4
5
6
# 备份全部数据库
mysqldump -uroot --all-databases > databases.sql

# 备份指定数据库
mysqldump -uroot databaseName > databaseName.sql

导入 sql 数据

1
2
3
4
5
6
# 导入
mysql -uroot < database.sql
# 或者
# 打开 mysql 客户端,使用 source 导入
mysql
source database.sql

Mysql/mariadb 主从数据库简单配置

安装并启动数据库

1
2
3
yum install mariadb-server mariadb -y
systemctl enable mariadb
systemctl start mariadb

master 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vi /etc/my.cnf
# 添加配置,注意 id 不能重复
# log-bin=mysql-bin
# server-id=1
# 配置完成后重启数据库
systemctl restart mariadb

# 进入mysql
mysql -uroot
# 添加slave用户
GRANT REPLICATION SLAVE ON *.* to 'slave'@'%' identified by '000000';
# 刷新权限
FLUSH PRIVILEGES;

slave 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vi /etc/my.cnf
# 只需要添加server-id
# server-id=2
# 配置完成后重启数据库
systemctl restart mariadb

# 进入mysql
mysql
# 添加主节点配置
# log_file 和 log_pos 的值在主节点上使用 show master status; 查看
change master to master_host='192.168.0.178',master_user='slave',master_password='000000',master_log_file='mysql-bin.000001' ,master_log_pos=463;

# 启动 slave 节点
start slave;

# 查看主从状态
show slave status\G;
# 如下显示则成功
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes

如果主从节点数据不同步需要手动同步

1
2
3
4
5
# 全局锁库
FLUSH TABLES WITH READ LOCK;

# 解锁
UNLOCK TABLES;

Hive 练习题解答

题目

在 Github 上面找了一些题目来练习
https://github.com/zzyb/Hive_interview-question

解答

answer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
 -- 第一道面试题
-- 编写Hive的HQL语句求出连续三天登陆的用户id
select distinct user_id
from (
select user_id,
lag(login_date,1) over (partition by user_id order by login_date) prev,
login_date now,
lead(login_date,1) over (partition by user_id order by login_date) next
from login_log) t where datediff(now,prev) = 1 and datediff(next,now)=1;

-- 第一道面试题
-- 编写Hive的HQL语句求出每 个店铺的当月销售额和累计到当月的总销售额

select name,mon,monMoney,accMonMoney
from(
select name,mon,sum(monmoney) over (partition by name,mon) monMoney,
sum(monmoney) over (partition by name order by mon) accMonMoney,
row_number() over (partition by name,mon order by mon) top
from fangwen2)t where top=1;

-- 第二道面试题
-- 编写Hive的HQL语句求出每个用户截止到每月为止的最大单月访问次数和累计到该月的总访问次数
select usr,mon,num,col1 monPv,col2 maxMonPv,col3 accMonPv from(
select usr,mon,num,sum(num) over (partition by usr,mon) col1,
max(num) over (partition by usr,mon) col2,
sum(num) over (partition by usr order by mon) col3,
row_number() over (partition by usr,mon order by mon) col4 from fangwen) t where col4=1;

select usr,mon,maxPv,totalPv from(
select usr,mon,num,
max(num) over (partition by usr,mon) maxPV,
sum(num) over (partition by usr order by mon) totalPv,
row_number() over (partition by usr,mon order by mon) top
from fangwen) t where top=1;


-- 第三道面试题
-- 编写Hive的HQL语句按照day和mac分组,求出每组的销量累计总和,追加到每条记录的后面
select day,mac,color,num,
sum(num) over (partition by day,mac)
from mac;


-- TopN
-- 第六道面试题

-- 1、求出每一年的最高温度(年份,最高温度)
select t.c_year,max(t.c_temp) from (
select year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) c_year,substr(line,9,2) c_temp from wendu
) t group by t.c_year;

select dt,year(dt),temp
from(
select concat_ws('-',substring(line,0,4),substring(line,5,2),substring(line,7,2)) dt,
substring(line,9,2) temp,
row_number() over (partition by substring(line,0,4) order by substring(line,9,2) DESC) top
from wendu) subT where top=1;

-- 2、求出每一年的最高温度是那一天(日期, 最高温度)

select dt,temp
from(
select concat_ws('-',substring(line,0,4),substring(line,5,2),substring(line,7,2)) dt,
substring(line,9,2) temp,
row_number() over (partition by substring(line,0,4) order by substring(line,9,2) DESC) top
from wendu) subT where top=1;

select t.c_year,t.c_date,t.c_max from
(select
year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) c_year,
from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd')) c_date,
row_number() over(partition by year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) order by substr(line,9,2) DESC) as c_rank,
substr(line,9,2) c_max
from wendu) t where t.c_rank = 1;

-- 第四道面试题
-- 求出所有数学课程成绩 大于 语文课程成绩的学生的学号
-- 方法1 case when
select * from(
select sid,
max(case course when 'yuwen' then course end) yuwen,
max(case course when 'yuwen' then score end) yuwenScore,
max(case course when 'shuxue' then course end) shuxue,
max(case course when 'shuxue' then score end) shuxueScore,
max(case course when 'yingyu' then course end) yingyu,
max(case course when 'yingyu' then score end) yingyuScore
from chengji group by sid) t where t.shuxueScore > t.yuwenScore;

-- 方法2 join
select sid,t1score yuwen,t2score shuxue from(
select * from (
select sid,course,score t1score
from chengji
where course = 'yuwen'
) t1
join (
select sid,course,score t2score from chengji where course = 'shuxue'
) t2 on t1.sid = t2.sid) t3 where t3.t2score > t3.t1score;

-- 第五道面试题
-- id_course.id id_course.course
-- 1 a
-- 1 b
-- 1 c
-- 1 e
-- 2 a
-- 2 c
-- 2 d
-- 2 f
-- 3 a
-- 3 b
-- 3 c
-- 3 e
-- 转换为
-- id a b b c d e f
-- 1 1 1 1 1 0 1 0
-- 2 1 0 0 1 1 0 1
-- 3 1 1 1 1 0 1 0

select id,
max(case course when 'a' then 1 ELSE 0 end) a,
max(case course when 'b' then 1 ELSE 0 end) b,
max(case course when 'b' then 1 ELSE 0 end) b,
max(case course when 'c' then 1 ELSE 0 end) c,
max(case course when 'd' then 1 ELSE 0 end) d,
max(case course when 'e' then 1 ELSE 0 end) e,
max(case course when 'f' then 1 ELSE 0 end) f
from id_course group by id;

-- 第七道面试题
-- 1,huangbo,45,a-c-d-f
-- 2,xuzheng,36,b-c-d-e
-- 3,huanglei,41,c-d-e
-- 4,liushishi,22,a-d-e
-- 5,liudehua,39,e-f-d
-- 6,liuyifei,35,a-d-e
-- 求出每种爱好中,年龄最大的人
select id,name,maxAge,favor
from (
select id, name, max(age) over (partition by favor) maxAge, favor,
row_number() over (partition by favor order by age DESC) top
from aihao lateral VIEW explode(split(favors, '-')) t1 as favor
)t2 where top=1;
-- 列出每个爱好年龄最大的两个人,并且列出名字。
select id,name,maxAge,favor
from (
select id, name, max(age) over (partition by favor) maxAge, favor,
row_number() over (partition by favor order by age DESC) top
from aihao lateral VIEW explode(split(favors, '-')) t1 as favor
)t2 where top<=2;

-- 第十道面试题
-- 日期,产品id,产品当日收入,产品当日成本
-- 2018-03-01,a,3000,2500
-- 2018-03-01,b,4000,3200
-- 2018-03-01,c,3200,2400
-- 2018-03-01,d,3000,2500
-- 2018-03-02,a,3000,2500
-- 2018-03-02,b,1500,800
-- 2018-03-02,c,2600,1800
-- 2018-03-02,d,2400,1000
-- 2018-03-03,a,3100,2400
-- 2018-03-03,b,2500,2100
-- 2018-03-03,c,4000,1200
-- 2018-03-03,d,2500,1900
-- 2018-03-04,a,2800,2400
-- 2018-03-04,b,3200,2700
-- 2018-03-04,c,2900,2200
-- 2018-03-04,d,2700,2500
-- 2018-03-05,a,2700,1000
-- 2018-03-05,b,1800,200
-- 2018-03-05,c,5600,2200
-- 2018-03-05,d,1200,1000
-- 2018-03-06,a,2900,2500
-- 2018-03-06,b,4500,2500
-- 2018-03-06,c,6700,2300
-- 2018-03-06,d,7500,5000
-- 2018-04-01,a,3000,2500
-- 2018-04-01,b,4000,3200
-- 2018-04-01,c,3200,2400
-- 2018-04-01,d,3000,2500
-- 2018-04-02,a,3000,2500
-- 2018-04-02,b,1500,800
-- 2018-04-02,c,4600,1800
-- 2018-04-02,d,2400,1000
-- 2018-04-03,a,6100,2400
-- 2018-04-03,b,4500,2100
-- 2018-04-03,c,6000,1200
-- 2018-04-03,d,3500,1900
-- 2018-04-04,a,2800,2400
-- 2018-04-04,b,3200,2700
-- 2018-04-04,c,2900,2200
-- 2018-04-04,d,2700,2500
-- 2018-04-05,a,4700,1000
-- 2018-04-05,b,3800,200
-- 2018-04-05,c,5600,2200
-- 2018-04-05,d,5200,1000
-- 2018-04-06,a,2900,2500
-- 2018-04-06,b,4500,2500
-- 2018-04-06,c,6700,2300
-- 2018-04-06,d,7500,5000

-- 源数据3月份与4月份日利润相同,添加一条数据使其不同。
insert into goods values ('2018-04-07','d','5000','3000');

-- 1. 输出每个产品,在2018年期间,每个月的净利润,日均成本。
select d_month,profit,avgCost from (
select concat(year(dt),'-',month(dt)) d_month,name,income,cost,
(sum(income) over(partition by concat(year(dt),'-',month(dt))) -
sum(cost) over(partition by concat(year(dt),'-',month(dt)))) profit,
avg(cost) over(partition by concat(year(dt),'-',month(dt))) avgCost,
row_number() over (partition by concat(year(dt),'-',month(dt)) order by name) top
from goods where year(dt) = '2018') t_month where t_month.top=1;

-- 2. 输出每个产品,在2018年3月中每一天与上一天相比,成本的变化。
select
dt,cost,
(cost - LAG(cost,1,cost) over(partition by concat(year(dt),'-',month(dt)) order by dt))
from goods where concat(year(dt),'-',month(dt)) = '2018-3';

-- 3. 输出2018年4月,有多少个产品总收入大于22000元,必须用一句SQL语句实现,且不允许使用关联表查询、子查询。
select concat(year(dt),'-',month(dt)),name,sum(income) sumIncome
from goods where concat(year(dt),'-',month(dt)) = '2018-4'
group by name,concat(year(dt),'-',month(dt)) having sumIncome>22000;

-- 4. 输出2018年4月,总收入最高的那个产品,每日的收入,成本,过程使用over()函数。
select g.dt,maxT.name,maxT.maxIncome,g.income,g.cost from(
select name,totalIncome,max(totalIncome) over() maxIncome from(
select
dt,name,income,cost,
sum(income) over (partition by name) totalIncome
from goods where concat(year(dt),'-',month(dt)) = '2018-4') t limit 1
) maxT join goods g on maxT.name=g.name where concat(year(dt),'-',month(dt)) = '2018-4';



Spark SQL 读写操作

使用方法

  • spark-shell

  • spark-sql

  • scala code

数据源

  • CSV

  • JSON

  • Parquet

  • ORC

  • JDBC/ODBC connections

  • Plain-text files

读取示例

1
2
3
4
5
6
7
8
9
10
// 格式
DataFrameReader.format(...).option("key", "value").schema(...).load()

// 示例
spark.read.format("csv")
.option("mode", "FAILFAST") // 读取模式
.option("inferSchema", "true") // 是否自动推断 schema
.option("path", "path/to/file(s)") // 文件路径
.schema(someSchema) // 使用预定义的 schema
.load()
读模式 描述
permissive 当遇到损坏的记录时,将其所有字段设置为 null,并将所有损坏的记录放在名为 _corruption t_record 的字符串列中
dropMalformed 删除格式不正确的行
failFast 遇到格式不正确的数据时立即失败

写入示例

1
2
3
4
5
6
7
8
9
// 格式
DataFrameWriter.format(...).option(...).partitionBy(...).bucketBy(...).sortBy(...).save()

//示例
dataframe.write.format("csv")
.option("mode", "OVERWRITE") //写模式
.option("dateFormat", "yyyy-MM-dd") //日期格式
.option("path", "path/to/file(s)")
.save()
Scala/Java 描述
SaveMode.ErrorIfExists 如果给定的路径已经存在文件,则抛出异常,这是写数据默认的模式
SaveMode.Append 数据以追加的方式写入
SaveMode.Overwrite 数据以覆盖的方式写入
SaveMode.Ignore 如果给定的路径已经存在文件,则不做任何操作

常用读写示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 简写法
spark.read.格式("路径")
spark.read.json("/usr/file/json/emp.json")

// 读取csv
spark.read.format("csv")
.option("header", "false") // 文件中的第一行是否为列的名称
.option("mode", "FAILFAST") // 是否快速失败
.option("inferSchema", "true") // 是否自动推断 schema
.load("/usr/file/csv/dept.csv")
.show()
// 写入csv,指定分隔符\t
df.write.format("csv").mode("overwrite").option("sep", "\t").save("/tmp/csv/dept2")

// json 读
spark.read.format("json").option("mode", "FAILFAST").load("/usr/file/json/dept.json").show(5)

// json 写
df.write.format("json").mode("overwrite").save("/tmp/spark/json/dept")

// Parquet 读
spark.read.format("parquet").load("/usr/file/parquet/dept.parquet").show(5)

// Parquet 写
df.write.format("parquet").mode("overwrite").save("/tmp/spark/parquet/dept")

// 数据库读
spark.read
.format("jdbc")
.option("driver", "com.mysql.jdbc.Driver") //驱动
.option("url", "jdbc:mysql://127.0.0.1:3306/mysql") //数据库地址
.option("dbtable", "help_keyword") //表名
.option("user", "root").option("password","root").load().show(10)

// 数据库写
val df = spark.read.format("json").load("/usr/file/json/emp.json")
df.write
.format("jdbc")
.option("url", "jdbc:mysql://127.0.0.1:3306/mysql")
.option("user", "root").option("password", "root")
.option("dbtable", "emp")
.save()

Hbase Shell 常用操作

help

1
help 'create'

hbase shell 操作

HBase是没有schema的,就是在创建表的时候不需要指定表中有哪些列,只需要指定有多少个列蔟

1
2
# 创建订单表,表名为ORDER_INFO,该表有一个列蔟为C1
create "ORDER_INFO","C1"
1
2
# 查看表
list
1
2
3
4
5
# 删除表
# 1.disable "TableName"
# 2.drop "TableName"
disable "ORDER_INFO"
drop "ORDER_INFO"

增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 添加一条数据
# put '表名','ROWKEY','列蔟名:列名','值'
# 数据示例
# ID STATUS PAY_MONEY PAYWAY USER_ID OPERATION_DATE CATEGORY
# 000001 已提交 4070 1 4944191 2020-04-25 手机
put "ORDER_INFO", "000001", "C1:STATUS", "已提交"
put "ORDER_INFO", "000001", "C1:PAY_MONEY", 4070
put "ORDER_INFO", "000001", "C1:PAYWAY", 1
put "ORDER_INFO", "000001", "C1:USER_ID", "4944191"
put "ORDER_INFO", "000001", "C1:OPERATION_DATE", "2020-04-25"
put "ORDER_INFO", "000001", "C1:CATEGORY", "手机"

# 查询数据
# get 查询一行数据
# get '表名',‘ROWKEY’
get 'ORDER_INFO','000001'

# 正确显示中文
get "ORDER_INFO", "000001", {FORMATTER => 'toString'}

# 显示多版本
get 'cx_table_stu01','20200001',{COLUMNS=>'cf1',VERSIONS=>5}
# 如果只能显示一个版本,查看表结构的version是不是为1
desc 'cx_table_stu01'

# 除了列(COLUMNS)修饰词外HBase还支持
# Limit(限制查询结果行数)
# STARTROW (ROWKEY起始行。会先根据这个key定位到region,再向后扫描)
# STOPROW(结束行)
# TIMERANGE(限定时间戳范围)
# VERSIONS(版本数)
# FILTER(按条件过滤行)

# 更新数据
# put '表名','ROWKEY','列蔟名:列名','更改值'
# 将订单ID为000001的状态,更改为「已付款」
put "ORDER_INFO","000001", "C1:STATUS", "已付款"

# 删除数据
# 删除列
# 如果有多个版本只会删除当前版本
# delete "表名", "ROWKEY", "列蔟名:列名"
# 将订单ID为000001的状态列删除。
delete "ORDER_INFO", "000001", "C1:STATUS"

# 删除行
# deleteall '表名','ROWKEY'
# 将订单ID为000001的信息全部删除(删除所有的列)
deleteall "ORDER_INFO", "000001"

# 增加/删除列族
# 增加
# alter '表名', '列族名'
alter 'Student', 'teacherInfo'

# 删除
# alter '表名', {NAME => '列族名', METHOD => 'delete'}
alter 'Student', {NAME => 'teacherInfo', METHOD => 'delete'}

# 修改表
# alter '表名',{name=>"列族",要修改的内容=>xxx}
alter 'PERSON3',{NAME=>'cf1', VERSIONS=>5}

删除数据的时候,其实 HBase 不是真的直接把数据删除掉,而是给某个列设置一个标志,然后查询数据的时候,有这个标志的数据,就不显示出来
什么时候真正的删除数据呢?

  • 后台进程,专门来执行删除数据的操作

执行 delete 的时候

  • 如果表中的某个列有对一个的几次修改,它会删除最近的一次修改
  • 默认是保存 1 个保存的时间戳
  • 有一个 version 属性

计数器

数据量小/测试环境中计数

1
2
3
# 统计表行数
# count "表名"
count "ORDER_INFO"

count 执行效率非常低,适用于百万级以下的小表 RowCount 统计

数据量大/生成环境中计数

利用 hbase.RowCounter 包执行 MR 任务

  • 启动yarn集群
  • 启动mr-historyserver
1
2
# 在 shell 中执行
hbase org.apache.hadoop.hbase.mapreduce.RowCounter '表名'

扫描操作

scan操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 全表扫描:scan "表名"(慎用,效率很低)
# 查询订单所有数据
scan "ORDER_INFO", {FORMATTER => 'toString'}

# 只查询列族”cf1”
scan 'cx_table_stu01',{COLUMNS=>'cf1'}
# 只查询列族”cf1”下的”name”信息
scan 'cx_table_stu01',{COLUMNS=>'cf1:name'}

# 限定只显示多少条: scan "表名", {LIMIT => XXX}
# 查询订单数据(只显示3条)
scan "ORDER_INFO", {FORMATTER => 'toString', LIMIT => 3}

# 指定查询某几个列: scan "表名", {LIMIT => XXX, COLUMNS => []}
# 只查询订单状态以及支付方式,并且只展示3条数据
scan "ORDER_INFO", {FORMATTER => 'toString', LIMIT => 3, COLUMNS => ['C1:STATUS', 'C1:PAYWAY']}

# 根据ROWKEY来查询:scan "表名", {LIMIT => XXX, COLUMNS => [], ROWPREFIXFILTER => 'ROWKEY'}
# 使用scan来根据rowkey查询数据,也是查询指定列的数据
scan "ORDER_INFO", {ROWPREFIXFILTER => '02602f66-adc7-40d4-8485-76b5632b5b53',FORMATTER => 'toString', LIMIT => 3, COLUMNS => ['C1:STATUS', 'C1:PAYWAY']}

过滤器

其实在hbase shell中,执行的ruby脚本,背后还是调用hbase提供的Java API

官方文档:https://hbase.apache.org/devapidocs/index.html

使用方法

1
2
3
4
5
# 查看内置过滤器
show_filters

# 示例
scan '表名',{Filter => "过滤器(比较运算符, '比较器表达式')"}
1
2
3
# 使用 RowFilter 查询指定订单ID的数据
# 查询订单的ID为:02602f66-adc7-40d4-8485-76b5632b5b53、订单状态以及支付方式
scan "ORDER_INFO", {FILTER => "RowFilter(=,'binary:02602f66-adc7-40d4-8485-76b5632b5b53')", COLUMNS => ['C1:STATUS', 'C1:PAYWAY'], FORMATTER => 'toString'}

RowFilter

通过上图,可以分析得到,RowFilter过滤器接受两个参数,

  • op——比较运算符

  • rowComparator——比较器

所以构建该Filter的时候,只需要传入两个参数即可

1
2
3
4
5
# 查询状态为「已付款」的订单
scan "ORDER_INFO",{FILTER => "SingleColumnValueFilter('C1', 'STATUS', = , 'binary:已付款')",COLUMNS => ['C1:STATUS','C1:PAYWAY'],FORMATTER => 'toString'}

# 查询支付方式为1,且金额大于3000的订单
scan "ORDER_INFO",{FILTER => "SingleColumnValueFilter('C1', 'PAYWAY', = , 'binary:1') AND SingleColumnValueFilter('C1', 'PAY_MONEY', > , 'binary:3000')",COLUMNS => ['C1:STATUS','C1:PAYWAY','C1:PAY_MONEY'],FORMATTER => 'toString'}

HBase shell中比较默认都是字符串比较,所以如果是比较数值类型的,会出现不准确的情况

例如:在字符串比较中4000是比100000大的

rowkey 过滤器

RowFilter 实现行键字符串的比较和过滤
PrefixFilter rowkey前缀过滤器
KeyOnlyFilter 只对单元格的键进行过滤和显示,不显示值
FirstKeyOnlyFilter 只扫描显示相同键的第一个单元格,其键值对会显示出来
InclusiveStopFilter 替代 ENDROW 返回终止条件行

列过滤器

FamilyFilter 列簇过滤器
QualifierFilter 列标识过滤器,只显示对应列名的数据
ColumnPrefixFilter 对列名称的前缀进行过滤
MultipleColumnPrefixFilter 可以指定多个前缀对列名称过滤
ColumnRangeFilter 过滤列名称的范围

值过滤器

ValueFilter 值过滤器,找到符合值条件的键值对
SingleColumnValueFilter 在指定的列蔟和列中进行比较的值过滤器
SingleColumnValueExcludeFilter 排除匹配成功的值

其他过滤器

ColumnPaginationFilter 对一行的所有列分页,只返回 [offset,offset+limit] 范围内的列
PageFilter 对显示结果按行进行分页显示
TimestampsFilter 时间戳过滤,支持等值,可以设置多个时间戳
ColumnCountGetFilter 限制每个逻辑行返回键值对的个数,在 get 方法中使用
DependentColumnFilter 允许用户指定一个参考列或引用列来过滤其他列的过滤器

比较器

比较器 描述
BinaryComparator 匹配完整字节数组
BinaryPrefixComparator 匹配字节数组前缀
BitComparator 匹配比特位
NullComparator 匹配空值
RegexStringComparator 匹配正则表达式
SubstringComparator 匹配子字符串

比较器表达式

比较器 表达式语言缩写
BinaryComparator binary:值
BinaryPrefixComparator binaryprefix:值
BitComparator bit:值
NullComparator null
RegexStringComparator regexstring:正则表达式
SubstringComparator substring:值

累加器(INCR)

incr可以实现对某个单元格的值进行原子性计数。语法如下:

incr ‘表名’,’rowkey’,’列蔟:列名’,累加值(默认累加1)

如果某一列要实现计数功能,必须要使用incr来创建对应的列

使用put创建的列是不能实现累加的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 通过 scan/get 无法直接查看cnt
hbase(main):004:0> scan "NEWS_VISIT_CNT", {FORMATTER => 'toString', LIMIT => 3}
ROW COLUMN+CELL
0000000001_00:00-01:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.890, value=

0000000001_00:00-01:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.153, value=00:00-01:00
0000000002_01:00-02:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.907, value=

0000000002_01:00-02:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.165, value=01:00-02:00
0000000003_02:00-03:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.912, value={
0000000003_02:00-03:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.171, value=02:00-03:00
3 row(s)
Took 0.1625 seconds
# 需要使用 get_counter
# get_counter '表','ROWKEY',"列蔟名:列名"
get_counter 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'
1
2
3
# 对0000000020新闻01:00 - 02:00访问计数+1
# incr '表','ROWKEY',"列蔟名:列名"
incr 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'

实用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 显示服务器状态
status

# 显示HBase当前用户
whoami

# 查看表结构
describe 'TableName'

# 查看表是否存在
exists 'TableName'

# 查看表是否禁用/启用
is_enabled 'TableName'
is_disabled 'TableName'

# 新增列蔟
alter 'TableName', 'C3'
# 删除列蔟
alter 'TableName', 'delete' => 'C3'

# 清空表数据
truncate 'TableName'

执行 hbase shell 脚本

1
2
# hbase shell 'script file path'
hbase shell ~/ORDER_INFO.txt

tips

web默认端口16010

hive HQL 常用操作

资料

https://blog.csdn.net/u013411339/article/details/120795941

远程连接hive

https://blog.csdn.net/qq_44065303/article/details/105820435

服务端要开启 hiveserver2

建表方式

1
2
3
4
5
6
7
8
9
10
11
12
-- 1.正常建表
-- 建表后无数据
create table tableName(col1 int,col2 int);

-- 2.查询建表 as
-- 建表后有数据,数据为查询结果
-- 并不会带原表的分区(分区丢失),包扣一些字段的约束等
create table tableName as select * from tableA;

-- 3.结构建表 like
-- 建表后无数据,表结构从 tableA 中复制
create table tableName like tableA;

函数查询

1
2
3
4
5
6
7
8
9
10
11
12
-- 查看内置函数
SHOW FUNCTIONS;

-- 查看month相关的函数
SHOW FUNCTIONS LIKE '*month*';

-- 查看函数用法
DESC FUNCTION function_name;

-- 查看 add_months 函数的详细说明并举例
DESC FUNCTION EXTENDED add_months;

CTE

1
2
3
4
-- 公用表表达式
-- 创建一个临时结果集
with tableName as (select * from tableB)
select * from tableName;

时间转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
语法: unix_timestamp()
返回值: bigint
说明: 获得当前时区的UNIX时间戳
hive> select unix_timestamp() from tableName;
1616906976


语法: from_unixtime(bigint unixtime[, string format])
返回值: string
说明: 转化UNIX时间戳(从1970-01-01 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式
hive> select from_unixtime(1616906976,'yyyyMMdd') from tableName;
20210328

语法: unix_timestamp(string date)
返回值: bigint
说明: 转换格式为"yyyy-MM-dd HH:mm:ss"的日期到UNIX时间戳。如果转化失败,则返回0
hive> select unix_timestamp('2021-03-08 14:21:15') from tableName;
1615184475

语法: unix_timestamp(string date, string pattern)
返回值: bigint
说明: 转换pattern格式的日期到UNIX时间戳。如果转化失败,则返回0
hive> select unix_timestamp('2021-03-08 14:21:15','yyyyMMdd HH:mm:ss') from tableName;
1615184475

语法: to_date(string timestamp)
返回值: string
说明: 返回日期时间字段中的日期部分。
hive> select to_date('2021-03-28 14:03:01') from tableName;
2021-03-28

语法: year(string date)
返回值: int
说明: 返回日期中的年。
hive> select year('2021-03-28 10:03:01') from tableName;
2021
hive> select year('2021-03-28') from tableName;
2021

日期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
语法: datediff(string enddate, string startdate)
返回值: int
说明: 返回结束日期减去开始日期的天数。
hive> select datediff('2020-12-08','2012-05-09') from tableName;
213

语法: date_add(string startdate, int days)
返回值: string
说明: 返回开始日期startdate增加days天后的日期。
hive> select date_add('2020-12-08',10) from tableName;
2020-12-18

语法: date_sub (string startdate, int days)
返回值: string
说明: 返回开始日期startdate减少days天后的日期。
hive> select date_sub('2020-12-08',10) from tableName;
2020-11-28

窗口函数

窗口聚合函数

sum

1
2
3
4
5
-- 共4种用法
sum(...) over() -- 相当于对全表进行求和
sum(...) over(order by ...) -- 连续累积求和,排序,不分组
sum(...) over(partition by ...) -- 同组内所行求和,只分组不排序
sum(...) over(partition by ... order by ...) -- 在每个分组内,连续累积求和,分组+排序

partition by

partition by

order by

order by

窗口表达式

窗口表达式提供了一种控制行范围的能力如往前,往后n行

可以写多个列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 默认从第一行到当前行
sum(...) over(partition by ... order by ...)
sum(...) over(partition by ... order by ... , ...) -- 按多列分区
sum(...) over(partition by ... order by ... rows between unbounded preceding and current row)

-- 向前3行至当前行
sum(...) over(partition by ... order by ... rows between 3 preceding and current row)

-- 向前3行,向后1行
sum(...) over(partition by ... order by ... rows between 3 preceding and 1 following)

-- 当前行到最后一行
sum(...) over(partition by ... order by ... rows between current row and unbounded following)

-- 第一行至最后一行,即分组内所有行
sum(...) over(partition by ... order by ... rows between unbounded preceding and unbounded following)

窗口排序函数

1
2
3
4
-- row_number家族,在每个分组中,为每行分配一个从1开始的唯一序号
row_number -- 不考虑重复
rank -- 考虑重复,挤占后续位置
dense_rank -- 考虑重复,不挤占后续位置
1
2
3
4
5
6
7
-- 窗口排序函数 ntile
将每个分组内的数据分为指定的若干个桶内,并且为每一个桶分配一个桶编号

select cookieid,createtime,pv
ntile(3) over(partition by cookieid order by createtime)
from website_pv_info
order by cookieid,createtime;

抽样函数

随机抽样 random

随机,但是速度慢

1
2
select * from website_pv_info order by rand() limit 2;
select * from website_pv_info distribute by rand() sort by rand() limit 2;

block 抽样

速度快,但是不随机

1
2
3
4
5
6
-- 按行数抽样
select * from website_pv_info tablesample (1 rows);
-- 根据数据大小百分比抽样
select * from website_pv_info tablesample (50 percent);
-- 根据数据大小抽样,支持b,k,m,g
select * from website_pv_info tablesample (1B);

bucket table 基于分桶表抽样

速度快+随机

tablesample(BUCKET x OUT OF y [ON colname])
tablesample

1
2
3
-- 根据整行数据进行抽样
select * from website_pv_info tablesample ( bucket 1 out of 2 on rand());

类型转换

1
2
3
4
5
6
7
8
9
-- CAST可以转换的类型
-- 1.隐式类型转换规则如下
-- (1)任何整数类型都可以隐式地转换为一个范围更广的类型,如 TINYINT 可以转换成 INT,INT 可以转换成 BIGINT。
-- (2)所有整数类型、FLOAT 和 STRING 类型都可以隐式地转换成 DOUBLE。
-- (3)TINYINT、SMALLINT、INT 都可以转换为 FLOAT。
-- (4)BOOLEAN 类型不可以转换为任何其它的类型。
CAST('1' AS INT) -- 可以正常执行
CAST('X' AS INT) -- 执行失败返回 NULL

concat 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- CONCAT 函数用于将多个字符串连接成一个字符串。
-- CONCAT(str1,str2,…)
SELECT CONCAT(id,',',name) AS con FROM info LIMIT 1;返回结果为
+----------+
| con |
+----------+
| 1,BioCyc |
+----------+

-- CONCAT_WS() 指定参数之间的分隔符
SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;
+----------+
| con_ws |
+----------+
| 1_BioCyc |
+----------+

case when

1
2
3
4
5
6
7
8
9
-- 简单 case
CASE sex
WHEN '1' THEN '男'
WHEN '2' THEN '女'
ELSE '其他' END
-- 搜索 case
CASE WHEN sex = '1' THEN '男'
WHEN sex = '2' THEN '女'
ELSE '其他' END

UDTF(explode)和侧视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- explode 
-- 接收 array 和 map 转换成多行
select explode(`array`(1,2,3,4,5));
-- Result:
-- col
-- 1
-- 2
-- 3
-- 4
-- 5
select explode(`map`("name","xxx",'sex','男','age','13'));
-- Result:
-- key value
-- name xxx
-- sex 男
-- age 13

-- 侧视图结合 explode
-- 侧视图语法
select ... from tableA lateral view UDTF(xxx) 表别名 as 列别名1,列别名2,列别名3;

行列转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
-- 多行转多列
-- 利用case when
-- 原表
-- students.id students.name students.subject students.score
-- 1 小明 语文 87
-- 2 张三 语文 27
-- 3 王五 语文 69
-- 4 李四 语文 99
-- 5 小明 数学 86
-- 6 马六 数学 33
-- 7 李四 数学 44
-- 8 小红 数学 50
-- 转换后
-- name chinese math
-- 小明 87 86
-- 小红 0 50
-- 张三 27 0
-- 李四 99 44
-- 王五 69 0
-- 马六 0 33

select name,
max(case subject when '语文' then score else 0 end) chinese,
max(case subject when '数学' then score else 0 end) math
from students group by name;

-- 多行转单列
-- 转换前
-- cookie.cookieid cookie.createtime cookie.pv
-- cookie1 2015-04-11 5
-- cookie1 2015-04-12 7
-- cookie1 2015-04-10 1
-- cookie1 2015-04-13 3
-- cookie1 2015-04-14 2
-- cookie1 2015-04-15 4
-- cookie1 2015-04-16 4
-- cookie2 2015-04-10 2
-- cookie2 2015-04-11 3
-- cookie2 2015-04-12 5
-- cookie2 2015-04-13 6
-- cookie2 2015-04-14 3
-- cookie2 2015-04-15 9
-- cookie2 2015-04-16 7
-- 转换后
-- cookieid createtimes pvs
-- cookie1 ["2015-04-10","2015-04-11","2015-04-12","2015-04-13","2015-04-14","2015-04-15","2015-04-16"] [1,5,7,3,2,4,4]
-- cookie2 ["2015-04-10","2015-04-11","2015-04-12","2015-04-13","2015-04-14","2015-04-15","2015-04-16"] [2,3,5,6,3,9,7]


select cookieid,collect_list(createtime) createtimes,collect_list(pv) pvs from cookie group by cookieid;
-- 其他函数
COLLECT_LIST(col) -- 多行合并成一行,不去重
COLLECT_SET(col) -- 多行合并成一行,将某字段的值进行去重汇总
CONCAT([string|col],[string|col],[string|col],....) -- 返回输入字符串连接后的结果,支持任意个输入字符串
CONCAT_WS('分隔符',[array|map]) -- 指定分隔符


-- 多列转多行
-- 主要使用 union 关键字
-- 转换前
-- rows2cols.col1 rows2cols.col2 rows2cols.col3 rows2cols.col4
-- a 1 2 3
-- b 4 5 6
-- 转换后
-- _u1.col1 _u1.col2 _u1.col3
-- a c 1
-- a d 1
-- a e 1
-- b c 4
-- b d 4
-- b e 4

select col1,'c' as col2,col2 as col3 from rows2cols
union all
select col1,'d' as col2,col2 as col3 from rows2cols
union all
select col1,'e' as col2,col2 as col3 from rows2cols;

-- 单列转多行
-- 转换前
-- aihao.id aihao.name aihao.age aihao.favors
-- 1 huangbo 45 a-c-d-f
-- 2 xuzheng 36 b-c-d-e
-- 3 huanglei 41 c-d-e
-- 4 liushishi 22 a-d-e
-- 5 liudehua 39 e-f-d
-- 6 liuyifei 35 a-d-e
-- 转换后
-- id name age favorslist
-- 1 huangbo 45 a
-- 1 huangbo 45 c
-- 1 huangbo 45 d
-- 1 huangbo 45 f
-- 2 xuzheng 36 b
-- 2 xuzheng 36 c
-- 2 xuzheng 36 d
-- 2 xuzheng 36 e
-- 3 huanglei 41 c
-- 3 huanglei 41 d
-- 3 huanglei 41 e
-- 4 liushishi 22 a
-- 4 liushishi 22 d
-- 4 liushishi 22 e
-- 5 liudehua 39 e
-- 5 liudehua 39 f
-- 5 liudehua 39 d
-- 6 liuyifei 35 a
-- 6 liuyifei 35 d
-- 6 liuyifei 35 e

select id,name,age,t.col1 as favorsList from aihao lateral VIEW explode(split(favors,'-')) t as col1;

-- lateral VIEW + explode

常见案例

查看前百分之N

1
2
3
4
5
6
7
8
9
10
11
/*
NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型
ntile(n)和where sorter = m 构成 n/m,如:ntile(2)和where sorted = 1 构成显示所有列的1/2
20% = 1/5 ==> ntile(5),where sorted = 1
*/
SELECT *
FROM (
SELECT name, orderdate, cost, NTILE(5) OVER (ORDER BY orderdate DESC) sorted
FROM business
) t
WHERE sorted = 1;

TOPN 问题

1
2
3
select * from (
select ROW_NUMBER() over(partition by ... order by ... DESC) as rank from tableName
) t where t.rank < 3;

与前一天相比

1
2
3
4
5
LAG(列名,前几行,'默认值')
LAG(name, 1)
LAG(name, 1,'default')
-- 表示取前一条记录的name的值。
-- LEAD()函数与此类似,不过它是查询某字段的后N条记录的值。

连续登录/连续问题

连续登录/连续问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-- 原数据
1,2020-01-01
1,2020-01-02
1,2020-01-07
1,2020-01-08
1,2020-01-09
1,2020-01-10
2,2020-01-01
2,2020-01-02
2,2020-01-03
2,2020-01-04
3,2020-01-02
3,2020-01-03
3,2020-01-04

-- 两种方案
-- 1.自连接,构建笛卡尔积
-- 2. 窗口函数

-- 自连接
-- 两天的连续登录问题使用自连接比较好做,大于2天不适合用自连接
-- 1.自连接
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b;

-- 2.查找用户id相同并且时间差为1的记录
select *
from (
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b
) t where t.a_id=t.b_id and datediff(t.a_dt,t.b_dt)=1;
-- 提取id,并去重
select distinct t1.a_id
from (
select *
from (
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b
) t where t.a_id=t.b_id and datediff(t.a_dt,t.b_dt)=1) t1;


-- 窗口函数
-- 适合于连续N天的问题

select distinct user_id from (
select user_id,login_date,
date_add(login_date,4-1) as nextday,
lead(login_date,4-1) over (partition by user_id order by login_date) as nextlogin
from login_log
) t where nextday = nextlogin;

-- nextday,手动算出 N 天后的时间
-- nextlogin,向下取 N 行进行验证


级联/累加求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- 编写Hive的HQL语句求出每个用户截止到每月为止的最大单月访问次数和累计到该月的总访问次数
-- 原表
usr,mon,num
A,2015-01,5
A,2015-01,15
B,2015-01,5
A,2015-01,8
B,2015-01,25
A,2015-01,5
A,2015-02,4
A,2015-02,6
B,2015-02,10
B,2015-02,5
A,2015-03,16
A,2015-03,22
B,2015-03,23
B,2015-03,10
B,2015-03,11
-- 两种方法
-- 1.group by 加自连接
-- 2.窗口函数

-- 1.group by 加自连接
-- 过于复杂,不写了
-- https://www.bilibili.com/video/BV1L5411u7ae?p=126

-- 方法2
-- 窗口函数

-- 关键点是
sum(num) over (partition by usr order by mon) accPV

with t as (
select usr,mon,num,
row_number() over (partition by usr,mon order by num DESC) top,
sum(num) over (partition by usr order by mon) accPV
from fangwen
)select usr,mon,num,accPV from t where top=1;

关于我一巴掌拍坏硬盘导致博客源代码丢失一年半后重建博客这件事

前言

在上古时期,记忆已经渐渐模糊。只记得是一个午后,遥远的世界树在被邪恶的夜魇入侵,不知道那场战斗持续了多久,不知道是为什么而战,甚至最后的胜利消息也是通过只言片语所得知。我作为天辉方的爱由莎与夜魇的战斗是那么的焦灼。因为一次次的操作失误,一次次的倒下,头顶上的怒气条慢慢的攒满,直到那一巴掌的落下,怒气条空了。一开始,以为这只是一点桌面图标的消失、一个小小的弹窗提示、一个普通的报错,直到一整块硬盘的消失。当我从远古遗迹中回到现实位面,重新按下笔记本电脑开机键,一阵如炒豆子般的声音,引来方圆十里人们的围观,舍友们以为我偷偷在床上炒菜不分给他们。网上流行过一种说法,人死前会回忆他的一生,这时我相信这块硬盘也在回忆它的一生。宛如看见硬盘文件如到持续时间的幻象一样,消失得无影无踪。

太长不看:打游戏上头一巴掌拍坏机械硬盘。

步骤

在一年半之后终于有空(其实是懒)根据 github page 上面的发布内容重新做一个 hexo 的源码文件夹。

这里有个问题,重建 blog 需要几步呢?

A. 3

B. 5

C. 钝角

1. 重装一个 hexo

因为损坏的硬盘是 D 盘,hexo 是安装在 C 盘并不受影响,但是版本落后不少,在这一年半期间 npm 也升级过,在使用旧版 hexo 和 npm 碰到了兼容性问题,就直接重装 npm 和 hexo,也相当于进行一次升级。

直接在 nodejs 下载安装程序覆盖安装即可

2. 调整和优化一些设置

旧 blog 主题用的 icarus 也没找到其他好看的主题就继续沿用。

但是旧 blog 的 icarus 是自定义过的,就需要对着旧 blog 的样式调整一下。

3. 复制内容

将旧 blog 的 HTML 内容复制为 Markdown 格式。

这里使用的工具是 bejson 家的,挺好用。

但是也会遇到一些问题,如标题转换异常,还有通过 hexo 的高亮代码 codeblock 包裹的也会有这种转换问题,这就需要重新写过了。

  • 标题转换,感觉应该算特性,在 typora 里面看的话会不正常,在 hexo 生成的内容则是显示正常的。

标题转换错误

  • 有序列表的序号也会有错误,需要手动修改

有序列表的序号也会有错误

  • 图片题注格式不正常(不是默认格式),这里是因为转换后加上了 html 的标签,md 解析后格式不是默认格式,去掉 html 标签即可

4. 自定义 JS 的引入

这里需要引入自定义 JS 是因为第一篇内容里面写的代码需要。

也很简单吧 js 文件放到 themes\主题名称\source\js 就好,然后在 markdown 里面引用就行。

有一个小细节要注意的是,路径要以 / 开始,写绝对路径,相对路径如果打开到内容页的话会无法加载 js 文件。

5. 部署到 github page

首先安装一下 deployer

1
npm install hexo-deployer-git --save

然后添加一下 _config.yml 配置

1
2
3
4
deploy:
type: git
repo: https://github.com/MisakaWater/MisakaWater.github.io
branch: master

再运行一下

1
2
3
hexo clean
hexo g
hexo d

就开始推送了

其实整个过程难度不大,就是想记一下这个硬盘的故事,太逗了

安装与配置docker

系统用的是ubuntu16.04,过程参考官方文档https://docs.docker.com/engine/install/ubuntu/

这里只记录一下坑和小技巧

别换源

先使用系统自带源,别着急换国内源

之前安装docker换源后执行到下面的命令会报错,提示找不到docker的安装包

1
sudo apt-get install docker-ce docker-ce-cli containerd.io

这里我的解决方法是

开tsocks,使用官方源来安装docker

修改docker仓库源

安装完成后直接奔着 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 阿里的镜像去,感觉比其他的仓库源好用

有用的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#查看运行的容器
docker ps

#退出并在后台运行
Ctrl + P + Q

#进入容器
docker attach [CONTAINER ID]

#-v挂载
#譬如我要启动一个centos容器,宿主机的/test目录挂载到容器的/soft目录,可通过以下方式指定:
docker run -it -v /test:/soft centos /bin/bash
#冒号":"前面的目录是宿主机目录,后面的目录是容器内目录。

#进入docker交互模式
sudo docker exec -it [CONTAINER ID] /bin/bash

使用VUE时遇到关于符号连接的一个BUG

最近准备试一下ABP框架做个vue的spa应用,按着教程一步步走下去,走到运行yarn serve就报了如下错误

image-20200510125747213

网上查了一下以为是某些插件没装好,不过转念想想ABP这么牛的框架不会吧不会吧不会吧。

很奇怪的是把vue复制到其他文件夹可以正常运行,唯独在source文件夹里面不能正常运行,Google了一早上后打算不用vue,最后再仔细看了一下返回的报错

image-20200510130010805

才想起来source文件夹因为之前C盘空间不够移到D盘在mklink回C盘的,导致在C盘开Power shell运行D盘的文件就会报错。

解决方法

在D盘开Power shell运行yarn serve即可