# 云胡的编程周报第 014 期
时间:2023/11/13 - 2023/11/19
# 一、点滴记录
# 1
给「云胡图书馆」添加 https
。
申请域名时,域名服务商会给我们一个主域名,我的主域名是 yunhu.wiki
绑定了我的个人站,那么我的图书馆网站要放在哪里?
一个 IP
地址和一个端口可以唯一标识一个服务,那样的话,就可以用 yunhu.wiki:9049
来唯一确定我的图书馆网址。
这样又有一个问题,你把图书馆网站分享给别人,别人还要写上端口,不规范且不优雅。
我的最初解决方案是在「云解析 DNS
」中添加一条「隐形 URL
」 记录,用一个二级域名library.yunhu.wiki
指向 yunhu.wiki:9049
,这样用户表面上访问的是 library.yunhu.wiki
,实际上访问的是 yunhu.wiki:9049
。
回到正题,我们要给 library.yunhu.wiki
添加 https
,此时不能用隐形 URL
,因为在腾讯云「隐/显性 URL
记录」文档中可以看到如下说明:
URL 转发记录转发前,地址仅支持 HTTP、不支持 HTTPS;
转发后地址支持 HTTP 及 HTTPS ;
我们需要换一个方案,那就是使用 CNAME
记录,CNAME
记录可以将一个域名映射到另外一个域名,于是我将 library.yunhu.wiki
这个二级域名映射到 yunhu.wiki
这个主域名。
然后申请 SSL
证书,通过后下载 nginx
版本的证书,然后在 nginx
中配置,这边还有一个重要的知识点,以下是我的 nginx
配置文件。
server {
listen 80;
server_name yunhu.wiki;
rewrite ^(.*) https://$server_name$request_uri permanent;
}
server {
listen 443 ssl;
server_name yunhu.wiki;
# SSL 验证相关配置
ssl_certificate /etc/nginx/cert/yunhu.wiki.pem;
ssl_certificate_key /etc/nginx/cert/yunhu.wiki.key;
# 缓存有效期
ssl_session_timeout 5m;
# 加密算法
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
# 安全链接可选的加密协议
ssl_protocols TLSv1.2 TLSv1.3;
location / {
root /var/www/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
server {
listen 80;
server_name library.yunhu.wiki;
rewrite ^(.*) https://$server_name$request_uri permanent;
}
server {
listen 443 ssl;
server_name library.yunhu.wiki;
# SSL 验证相关配置
ssl_certificate /etc/nginx/cert/library.yunhu.wiki.pem;
ssl_certificate_key /etc/nginx/cert/library.yunhu.wiki.key;
# 缓存有效期
ssl_session_timeout 5m;
# 安全链接可选的加密协议
ssl_protocols TLSv1.2 TLSv1.3;
# 加密算法
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
location / {
root /root/library/library-web-vue/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~/api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://10.0.12.16:8090/;
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
access_log /var/log/nginx/library_access.log;
}
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
可以看到两个 server
块都监听了 80
和 443
端口,可能有人会说,你前面说过了 IP
和端口号确定唯一一个服务,你现在怎么两个服务都在 443
端口?
知识点来了,nginx
有一种虚拟主机技术,可以把一台物理服务器划分成多个虚拟的服务器,就好像拥有多台服务器一样,虚拟主机之间完全互相独立,彼此互不干扰。
这边用到的是里面的一种基于域名的虚拟主机, 通过 server_name
来确定要将请求发送到哪个虚拟主机,从而实现在一个 IP
地址上托管多个域名。
# 2
给「云胡图书馆」添加 https
后,发现无法访问腾讯云对象存储上的 epub
书籍了,产生了跨域问题。
解决方式:
点击存储桶,然后点击 「安全管理」 -> 「跨域访问 CORS
设置」 -> 「添加规则」 ,然后将新的地址 https://library.yunhu.wiki
填入即可。
# 3
我在设置好「云胡图书馆」的书籍目录后,发现点击二级章节没有跳动到二级章节,而是跳到了一级章节。
这是因为当父子元素都有点击事件的时候,点击子元素的事件,之后会触发父元素的事件,直到根元素,这个叫做事件的冒泡。
为了解决这个问题,我们要使用 .stop
修饰符来阻止事件冒泡。
<div @click.stop="selectInnerList(innerItem)"></div>
# 4
给「云胡图书馆」的目录美化,之前的目录在左侧,不太好看,改为新的样式组件抽屉 (Drawer ),点击可以从右到左侧边展开。
epub
格式的电子书本质上是一个 html
,它的样式是通过 css
生成的,epub
电子书的目录是一个数组,以下是一个示例:
盗墓笔记这本书的部分目录,太长了,我只截取部分。
href
是目标位置,使我们可以跳转到指定位置,比如某一章节。
label
是章节的中文名称
subitems
是子章节,子章节也可以有自己的子章节。
[
{
"id": "coverpage",
"href": "coverpage.html",
"label": "封面",
"subitems": []
},
{
"id": "chapter898",
"href": "chapter898.html",
"label": "总目录",
"subitems": []
},
{
"id": "chapter443",
"href": "chapter443.html",
"label": "第一季",
"subitems": [
{
"id": "chapter444",
"href": "chapter444.html",
"label": "第一卷 七星鲁王",
"subitems": [
{
"id": "chapter457",
"href": "chapter457.html",
"label": "第一章 血尸",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter458",
"href": "chapter458.html",
"label": "第二章 五十年后",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter459",
"href": "chapter459.html",
"label": "第三章 瓜子庙",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter460",
"href": "chapter460.html",
"label": "第四章 尸洞",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter461",
"href": "chapter461.html",
"label": "第五章 水影",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter462",
"href": "chapter462.html",
"label": "第六章 积尸地",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter463",
"href": "chapter463.html",
"label": "第七章 一百多个人头",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter464",
"href": "chapter464.html",
"label": "第八章 山谷",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter465",
"href": "chapter465.html",
"label": "第九章 古墓",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter466",
"href": "chapter466.html",
"label": "第十章 影子",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter467",
"href": "chapter467.html",
"label": "第十一章 七星棺",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter468",
"href": "chapter468.html",
"label": "第十二章 门",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter469",
"href": "chapter469.html",
"label": "第十三章 02200059",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter470",
"href": "chapter470.html",
"label": "第十四章 闷油瓶",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter471",
"href": "chapter471.html",
"label": "第十五章 屁",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter472",
"href": "chapter472.html",
"label": "第十六章 小手",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter473",
"href": "chapter473.html",
"label": "第十七章 洞",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter474",
"href": "chapter474.html",
"label": "第十八章 大树",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter475",
"href": "chapter475.html",
"label": "第十九章 女尸",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter476",
"href": "chapter476.html",
"label": "第二十章 钥匙",
"subitems": [],
"parent": "chapter444"
},
{
"id": "chapter477",
"href": "chapter477.html",
"label": "第二十一章 青眼狐尸",
"subitems": [],
"parent": "chapter444"
}
]
}
]
}
]
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
渲染出来的结果是:
对不同级别的章节做了不同的缩进层次,看起来比较美观。
# 5
为「云胡图书馆」添加在划线位置的 Tooltip
,上面再通过插槽添加三个按钮,分别是复制、高亮和分享书摘。
鼠标事件的三组属性:
- MouseEvent.pageX 和 MouseEvent.pageY
这边我们只探讨 Y
方向,X
方向的知识是类似的。
pageY
会返回触发事件的位置相对于整个文档的 Y
坐标值,由于书籍是一个可以下滑的文档,因此下滑之后,pageY
会一直增大,因此鼠标划线位置超过一屏,
Tooltip
位置就会在下方,无法实现我们要的功能。
- MouseEvent.clientX 和 MouseEvent.clientY
clentY
会返回鼠标指针在浏览器客户区域的垂直坐标,按理论来说页面向下滑动,这个垂直坐标还是相对于坐标原点,但是可能 epub.js
重写了这方面的逻辑,我还是获取不到。
- MouseEvent.screenX 和 MouseEvent.screenY
screenY
提供了事件发生时鼠标指针相对于用户屏幕的垂直坐标,这是我们想要的,由于事先不知道这个属性,然后又想监听滑动距离去算出相对于屏幕顶点的高度,花了大概 12
个小时也没弄好,走了很长的弯路,后面才知道有这个属性。
结果:
# 6
vuepress
添加 markdown
数学公式功能。
- 安装
markdown-it-katex
npm install markdown-it-katex
- 在配置文件中对其配置
// .vuepress/config.js
module.exports = {
head: [
['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1/katex.min.css' }],
['link', { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.10.0/github-markdown.min.css" }]
],
markdown: {
extendMarkdown: md => {
// 使用数学插件
md.use(require("markdown-it-katex"));
}
},
2
3
4
5
6
7
8
9
10
11
12
之后就可以愉快地在 markdown
中使用 Latex
数学公式了。
# 7
GitHub Copilot
是一款人工智能编程助手插件,他是 Github
和 OpenAI
合作研发的,我们写代码的时候,它可以自动提供代码补全,效果非常非常好。
有 VSCode
和 JetBrains
平台的插件。
有了它,更爱写代码了。
# 8
早期的时候,数据库存储用户密码是用明文存储。
比如我有一个账号:
账号:yunhu
密码:12345678
数据库的用户表中直接明文存储了密码 12345678
,如果数据库泄露,那就完了。
2011
年的时候,CSDN
六百多万条用户密码泄露,CSDN
就是明文存储了密码,因此我们在设计用户表的时候密码应该密文存储。
第一种方案:使用 md5
进行对明文密码进行哈希计算,得到一个 128
位的哈希值。
这种方法有个问题,那就是黑客有一个彩虹表,上面记录了常见的明文密码与 md5
值之间的映射关系,因此他可以通过这个彩虹表反向知道你的密码是多少。
比如他看到密码是 25d55ad283aa400af464c76d713c07ad
,他就知道明文密码是 12345678
。
因此这种方法依然不安全。
第二种方案:使用 md5
+ 盐(salt)进行加密。
盐在密码学中是一组随机数据,我们可以随机生成这个盐的值,比如 qwerty
,此时我们可以随机按某种顺序与密码结合。
比如我们把盐的值加到密码的前面,那么此时这个加盐后的密码变为:
qwerty12345678
,在对其进行 md5
哈希计算,会生成 d59c0e414426c0b261ba0cc19372ca1a
把盐的值和这个 128
位的哈希值存到数据库中。
即使数据库泄露,明文密码也不会泄露,因为黑客无法知道我们是如何与明文组合的,我们可能是盐值全部加在前面,也可能全部加在后面,也可能前面几个加在前面,后面几个加在后面。因此这种方式还是很安全的。
第三种方案:
使用 Spring Security
中的 BCryptPasswordEncoder
来进行加密,它已经在内部帮我们加好盐了。
public static void main(String[] args) {
// 创建 BCryptPasswordEncoder 实例
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 明文密码
String rawPassword = "12345678";
// 加密后的密码
String encodedPassword = passwordEncoder.encode(rawPassword);
// 验证密码
boolean isPasswordMatch = passwordEncoder.matches(rawPassword, encodedPassword);
System.out.println("密码匹配结果: " + isPasswordMatch);
}
2
3
4
5
6
7
8
9
10
11
# 9
Spring Boot
后端需要对请求进行拦截,必须是登陆的用户,请求带 token
的才能往下访问,平常我们设计一个通用的返回结果类,在自定义的拦截器中,重写 preHandle
方法,返回值限制了必须是 boolean
,那我们要如何给前端返回一个规范的 json
格式?
通过自定义 json
格式,然后写入到 response
这个响应对象之中就可以了。
public class UserLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// http 的 header中获得 token
String token = request.getHeader("authorization");
// token不存在
if (token == null || "".equals(token)) {
log.error("token not exist");
// 自定义响应消息的 JSON 格式
String jsonResponse = "{\"code\": 401, \"msg\": \"登录失败\", \"data\": null}";
// 设置 HTTP 状态码,例如 200 表示成功
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 设置响应的内容类型为 JSON
response.setContentType("application/json");
// 设置字符编码
response.setCharacterEncoding("UTF-8");
// 写入响应消息
response.getWriter().write(jsonResponse);
return false;
}
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果 token
不存在,返回自定义的 json
如下:
{
"code": 401,
"msg": "登录失败",
"data": null
}
2
3
4
5
# 10
CSS
的 flex
弹性布局
将 display
设为 flex
表示这是一个弹性布局。
flex-direction
的常用可选值有两个,分别是 row
水平排列,和 column
垂直排列,它定义了主轴的方向。
justify-content
控制 Flex
布局内的子元素在主轴上的对齐方式。
align-items
控制 Flex
布局内的子元素在交叉轴上的对齐方式,交叉轴与主轴垂直。
flex-wrap
属性表示如果一条轴线排不下,如何换行,它有三个属性值
nowrap
不换行,超过宽度会出现滑动条。wrap
换到下一行,必然不会有滑动条。wrap-reverse
换到上一行,必然不会有滑动条。
<div class="box">
<el-button type="danger">第一个按钮</el-button>
<el-button type="primary">第二个按钮</el-button>
<el-button type="success">第三个按钮</el-button>
</div>
<style scoped>
.box {
margin: 20px;
display: flex;
flex-direction: row;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
# 二、发现
# 1
context.js
http://lab.jakiestfu.com/contextjs/ (opens new window)
右键菜单 npm
包。
# 2
relingo
https://relingo.net/zh (opens new window)
浏览器扩展,翻译单词,颜值在线,也很实用。
# 3
sigil
https://sigil-ebook.com/ (opens new window)
编辑,制作 epub
电子书。
# 4
pdf2htmlEX
https://github.com/pdf2htmlEX/pdf2htmlEX (opens new window)
pdf
文档转为 html
,这个工具效果非常之好,本地安装十分复杂,推荐使用 docker
进行安装。