-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathatom.xml
448 lines (236 loc) · 336 KB
/
atom.xml
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>那个码农</title>
<subtitle>钟意的博客 | ThatCoder's Blog 钟意博客</subtitle>
<link href="https://blog.thatcoder.cn/atom.xml" rel="self"/>
<link href="https://blog.thatcoder.cn/"/>
<updated>2024-12-21T16:21:22.834Z</updated>
<id>https://blog.thatcoder.cn/</id>
<author>
<name>钟意</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Coze同插件不同工具之间代码复用</title>
<link href="https://blog.thatcoder.cn/dev/coze-import/"/>
<id>https://blog.thatcoder.cn/dev/coze-import/</id>
<published>2024-12-20T16:00:00.000Z</published>
<updated>2024-12-21T16:21:22.834Z</updated>
<content type="html"><![CDATA[<h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>用官方在线IDE的Node环境开发Coze插件的工具时,如果import复用其它模块定义好的函数、类、类型等,会出现类似如下报错:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">Error: Cannot find module 'xxx'<br><br>ESLint couldn't find an eslint.config.(js|mjs|cjs) file.<br></code></pre></td></tr></table></figure><p>问题已解决,急的话直接点击跳转到 <a href="#%E6%9C%80%E7%BB%88%E6%96%B9%E6%A1%88">最终方案</a> 部分。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20241222001921.png" alt="最终效果" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">最终效果</span></div></div><h2 id="分析原因"><a href="#分析原因" class="headerlink" title="分析原因"></a>分析原因</h2><p>毫无疑问,我们编写的IDE文件是一个ts文件,而Coze插件运行时是Node环境,Node环境运行时模块加载机制不能直接加载ts文件,因此需要先编译成js文件才能运行。</p><p>而编译过程中,如果遇到import语句,就会去查找对应的模块文件,但由于Node环境无法直接运行ts文件,因此会报错。</p><h2 id="尝试解决"><a href="#尝试解决" class="headerlink" title="尝试解决"></a>尝试解决</h2><p>我大致思考尝试了如下方案</p><ul><li>方案一:修改配置,但是我们无法修改IDE的配置。</li><li>方案二:用额外的包去支持ts文件,比如<code>ts-node</code>、<code>ts-node-dev</code>等。但是我们不能控制命令行。</li><li>方案三:用 <code>const {xxx} = require('../common/common')</code> 这种方式导入模块。但是这样会导致IDE没有注释提示且无法提示具体属性(导入的类型是一个any),无法自动补全。</li></ul><p>经过一番挣扎,方案三是最佳可行方案,起码能解决基本的模块导入问题。最后我们要解决的是IDE的注释提示和自动补全问题,也就是编译时类型推断问题。</p><h2 id="最终方案"><a href="#最终方案" class="headerlink" title="最终方案"></a>最终方案</h2><blockquote><p>虽然丑陋,但是好用。</p></blockquote><ul><li>举例定义一个通用请求工具</li></ul><figure class="highlight typescript"><figcaption><span>通用工具</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Args</span>, <span class="hljs-title class_">Logger</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/runtime'</span>;<br><span class="hljs-keyword">import</span> axios, { <span class="hljs-title class_">AxiosInstance</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;<br><br><span class="hljs-comment">// 省略handle函数</span><br><br><span class="hljs-comment">// 基础响应类型</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> <span class="hljs-title class_">HttpRes</span><T> = {<br> <span class="hljs-attr">code</span>: <span class="hljs-built_in">number</span><br> <span class="hljs-attr">message</span>: <span class="hljs-built_in">string</span><br> <span class="hljs-attr">result</span>: <span class="hljs-built_in">boolean</span><br> <span class="hljs-attr">data</span>: T[]<br>}<br><br><span class="hljs-comment">// 定义通用请求工具</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">BaseApi</span><T> {<br> <span class="hljs-keyword">private</span> <span class="hljs-attr">logger</span>: <span class="hljs-title class_">Logger</span>;<br> <span class="hljs-title function_">info</span>(<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">logger</span>.<span class="hljs-title function_">info</span>(...args);<br> }<br> <span class="hljs-attr">host</span>: <span class="hljs-built_in">string</span> = <span class="hljs-string">'https://example.com'</span>;<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">baseUrl: <span class="hljs-built_in">string</span>, logger: Logger</span>){<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">api</span> = axios.<span class="hljs-title function_">create</span>({<br> <span class="hljs-attr">baseURL</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">host</span> + baseUrl,<br> <span class="hljs-attr">headers</span>: {<br> <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span><br> }<br> });<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">logger</span> = logger;<br> }<br> <span class="hljs-keyword">async</span> <span class="hljs-title function_">get</span>(<span class="hljs-attr">url</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">params</span>: <span class="hljs-title class_">Object</span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">HttpRes</span><T> | <span class="hljs-literal">null</span>> {<br> <span class="hljs-keyword">const</span> res = (<span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">api</span>.<span class="hljs-title function_">get</span>(url, {params}))?.<span class="hljs-property">data</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">info</span>(url, res, params, {<span class="hljs-attr">count</span>: res?.<span class="hljs-property">data</span>?.<span class="hljs-property">length</span> || <span class="hljs-literal">null</span>})<br> <span class="hljs-keyword">return</span> res<br> }<br> <span class="hljs-comment">// async post()</span><br> <span class="hljs-comment">// ...</span><br>}<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> <span class="hljs-title class_">TOrder</span> = {}<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderApi</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">BaseApi</span><<span class="hljs-title class_">TOrder</span>> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">logger: Logger</span>) {<br> <span class="hljs-variable language_">super</span>(<span class="hljs-string">"/order"</span>, logger);<br> }<br> <span class="hljs-keyword">async</span> <span class="hljs-title function_">getOrders</span>(<span class="hljs-attr">userId</span>: <span class="hljs-built_in">string</span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">HttpRes</span><<span class="hljs-title class_">TOrder</span>> | <span class="hljs-literal">null</span>> {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">'/list'</span>, {userId})<br> }<br>}<br><span class="hljs-comment">// ... 省略其它API</span><br></code></pre></td></tr></table></figure><ul><li>在其它工具代码中导入,并使用 typeof import() 去获取类型信息,这样IDE能自动补全提示。</li></ul><figure class="highlight typescript"><figcaption><span>需求工具导入</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Args</span>, <span class="hljs-title class_">Logger</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/runtime'</span>;<br><span class="hljs-keyword">const</span> { <span class="hljs-title class_">OrderApi</span> }: { <span class="hljs-title class_">OrderApi</span>: <span class="hljs-keyword">typeof</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"../common/common"</span>).<span class="hljs-property">OrderApi</span> } = <span class="hljs-variable language_">module</span>.<span class="hljs-built_in">require</span>(<span class="hljs-string">"../common/common"</span>);<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">handler</span>(<span class="hljs-params">{ input, logger }: Args<Input></span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">Output</span>> {<br> <span class="hljs-keyword">const</span> userId = input.<span class="hljs-property">userId</span><br> <span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> <span class="hljs-title class_">OrderApi</span>(logger)<br> <span class="hljs-keyword">const</span> orders = <span class="hljs-keyword">await</span> api.<span class="hljs-title function_">get</span>(<span class="hljs-string">'/order'</span>, {userId})<br> <span class="hljs-keyword">return</span> {<br> <span class="hljs-attr">data</span>: orders<br> };<br>};<br></code></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>typeof import 是 TypeScript 提供的静态类型推断工具,它在 <strong>编译阶段</strong> 就能捕捉模块的导出结构,而无需等到运行时去加载实际模块。<br>这一特性让我们能够应付 Coze 插件运行时环境中无法使用 import 的限制,在编译时获取类型信息,而不必依赖模块是否能被实际解析。</p><p>至于为何 require() 能支持动态导入,是因为做了一些拦截并转译工作,使得 require() 运行能支持动态导入。</p><p>总之,在Coze的IDE的Node环境中,使用运行时依赖得靠 require(),而在编译时得到依赖类型得靠 typeof import() 去做静态类型检查。</p><h2 id="闲聊"><a href="#闲聊" class="headerlink" title="闲聊"></a>闲聊</h2><p>好久没更新博客,都忘了怎么发布文章,有闲暇时候还是多写写保持思考与输出。</p>]]></content>
<summary type="html">解决Coze插件运行时不能 import 其它工具的模块问题</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Coze" scheme="https://blog.thatcoder.cn/tags/Coze/"/>
</entry>
<entry>
<title>Pachelbel's Greatest Hit: The Ultimate Canon - 纪念帕海贝尔:终极卡农</title>
<link href="https://blog.thatcoder.cn/hi-res/Canon/"/>
<id>https://blog.thatcoder.cn/hi-res/Canon/</id>
<published>2024-08-13T02:00:00.000Z</published>
<updated>2024-08-13T16:47:32.169Z</updated>
<content type="html"><![CDATA[<h2 id="资源介绍"><a href="#资源介绍" class="headerlink" title="资源介绍"></a>资源介绍</h2><p>为了庆祝帕海贝尔350周年诞辰(1653/9/1),BMG唱片公司特别搜罗分佈在全球旗下的各领域知名乐手、乐团,以15首不同编曲、配器与演奏风格的卡农演出版本,来纪念这位音乐家。</p><p>专辑囊括了许多顶尖音乐家和极具时代意义的指标性演出版本,诸如在20世纪(1940年)第一个将“卡农”这首巴罗克杰作以大眾流行手法演出、带动古典音乐普及化功不可没的费德勒与他的小交响乐团;知名的法国百雅室内乐团的演出则是早期身历声录音时代(1970)广播电臺最热门的播放版本;长笛名家詹姆斯.高威自编自演的招牌长笛版,是一份保留了巴罗克原味的优雅版本;无与伦比的双钢琴演出,则是市调票选的人气首选版本;日本作曲家兼电子音乐大师富田勋的改编版,是最教人惊豔的现代版电子合成卡农;加拿大铜管乐团自编自演的管乐演出,展示出金属色泽的堂皇卡农;葛莱美奖女歌手克丽欧·莲恩的填词演唱版“何如、何处、何时”,成为风景独特的福音版;白金美声无伴奏重唱乐团展现了纯净圣洁的巴罗克正统;独一无二的“尺八与箏乐五重奏”版本巧妙地以东方古乐器呈现西方古乐;此外,被乐坛寄予厚望的英国古典吉他新秀克裏夫·卡洛首次面世的古典超技吉他独奏版;西班牙的古典吉他世家罗梅洛的吉他与音色合成器配合的协奏版,赋予出人意表的现代风貌;由伦敦市政厅弦乐团演出的终结乐章则忠实地以正统的巴罗克编制演出,让听眾亲炙原汁原味的正宗卡农。全专辑计有十轨全球首度重新编录版本,绝对能够满足无数卡农迷的重度搜藏欲。</p><h2 id="曲目风格"><a href="#曲目风格" class="headerlink" title="曲目风格"></a>曲目风格</h2><p><strong>管弦版永远的神,长笛的也很绝</strong></p><ol><li>Canon in D 室内管弦版卡农 </li><li>Canon in D 长笛协奏版卡农 </li><li>Canon (Over a Basso Ostinato) 钢琴二重奏版卡农 </li><li>Canon of the Three Stars 现代版电子合成卡农 </li><li>How, Where, When? (Canon in D)</li><li>Earth Angel - Williams 尘世天使 </li><li>Canon 铜管版卡农 </li><li>Canon in F F大调古乐器版卡农 </li><li>Variations on Pachelbel’s Canon in D 弦乐四重奏变奏版卡农 </li><li>Canon 超技吉他独奏版卡农 </li><li>Canon 美声无伴奏版卡农 </li><li>Sweet Home - Sakakibara, Dai </li><li>Canon 吉他&音色合成器版卡农 </li><li>Canon in D 跨界乐团版卡农 </li><li>Canon & Gigue in D 卡农与吉格舞曲</li></ol><h2 id="资源分享"><a href="#资源分享" class="headerlink" title="资源分享"></a>资源分享</h2><h3 id="试听地址"><a href="#试听地址" class="headerlink" title="试听地址"></a>试听地址</h3><p><a href="https://music.163.com/#/album?id=508055">网易云音乐</a></p><h3 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h3><div class="tag-plugin link dis-select"><a class="link-card plain" title="终极卡农(DSD)" href="https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%8D%A1%E5%86%9C" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">终极卡农(DSD)</span><span class="cap link footnote">https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%8D%A1%E5%86%9C</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h2 id="TIPS"><a href="#TIPS" class="headerlink" title="TIPS"></a>TIPS</h2><p>DSD类资源一般都比较大,对播放与监听设备可能要求较高。</p>]]></content>
<summary type="html">庆祝帕海贝尔350周年诞辰,15首不同演出版本,来纪念这位音乐家。DSD(DFF)| 2.8MHz/1bit</summary>
<category term="分享" scheme="https://blog.thatcoder.cn/categories/%E5%88%86%E4%BA%AB/"/>
<category term="音乐" scheme="https://blog.thatcoder.cn/tags/%E9%9F%B3%E4%B9%90/"/>
</entry>
<entry>
<title>圣诞快乐,劳伦斯先生 Merry Christmas Mr. Lawrence</title>
<link href="https://blog.thatcoder.cn/hi-res/Sakamoto-Ry%C5%ABichi/"/>
<id>https://blog.thatcoder.cn/hi-res/Sakamoto-Ry%C5%ABichi/</id>
<published>2024-08-12T02:00:00.000Z</published>
<updated>2024-08-13T14:58:20.738Z</updated>
<content type="html"><![CDATA[<h2 id="资源介绍"><a href="#资源介绍" class="headerlink" title="资源介绍"></a>资源介绍</h2><p><a href="https://movie.douban.com/subject/1303535/">《战场上的快乐圣诞 Merry Christmas Mr. Lawrence》</a></p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/1cb925fd-c967-4923-af99-266e05be10a7.jpeg" alt="《战场上的快乐圣诞 Merry Christmas Mr. Lawrence》" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">《战场上的快乐圣诞 Merry Christmas Mr. Lawrence》</span></div></div><p>电影就不多评价,总之主演里面有北野武和坂本龙一,两位都是很出色有趣。虽然坂本龙一是搞音乐,北野武后来成为大导演了。</p><p>想起朋友推荐过一本书<a href="https://weread.qq.com/web/reader/b6b32bc0717f290cb6b9cf2">《北野武的小酒馆》</a>,里面也提过坂本龙一的合作。朋友说我语言和性格像北野武。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240812124401.png" alt="《北野武的小酒馆》" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">《北野武的小酒馆》</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/3e15e0281a61b6ca652abcb6a31b0c6.jpg" alt="书中桥段" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">书中桥段</span></div></div><p>配乐的原声经典曲《Merry Christmas Mr. Lawrence》与后来坂本龙一演奏的有些不一样,后流行演奏版下面也会单独给出,出自<a href="https://music.sonyselect.net/page/album.html?id=6452">《夜(nacht)》</a>的收录曲。</p><h2 id="资源分享"><a href="#资源分享" class="headerlink" title="资源分享"></a>资源分享</h2><h3 id="电影原声专辑"><a href="#电影原声专辑" class="headerlink" title="电影原声专辑"></a>电影原声专辑</h3><h4 id="试听地址"><a href="#试听地址" class="headerlink" title="试听地址"></a>试听地址</h4><p><a href="https://music.163.com/#/album?id=47812">网易云音乐</a></p><h4 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h4><div class="tag-plugin link dis-select"><a class="link-card plain" title="圣诞快乐,劳伦斯先生 OST原声(DSD)" href="https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%9D%82%E6%9C%AC%E9%BE%99%E4%B8%80/Merry%20Christmas%20Mr.%20Lawrence" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">圣诞快乐,劳伦斯先生 OST原声(DSD)</span><span class="cap link footnote">https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%9D%82%E6%9C%AC%E9%BE%99%E4%B8%80/Merry%20Christmas%20Mr.%20Lawrence</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h3 id="流行单曲"><a href="#流行单曲" class="headerlink" title="流行单曲"></a>流行单曲</h3><h4 id="试听地址-1"><a href="#试听地址-1" class="headerlink" title="试听地址"></a>试听地址</h4><p><a href="https://music.163.com/#/song?id=4899152">网易云音乐</a></p><h4 id="下载地址-1"><a href="#下载地址-1" class="headerlink" title="下载地址"></a>下载地址</h4><div class="tag-plugin link dis-select"><a class="link-card plain" title="圣诞快乐,劳伦斯先生《夜》(DSD)" href="https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%9D%82%E6%9C%AC%E9%BE%99%E4%B8%80/%E5%A4%9C%20(nacht)/%E5%9C%A3%E8%AF%9E%E5%BF%AB%E4%B9%90,%20%E5%8A%B3%E4%BC%A6%E6%96%AF%E5%85%88%E7%94%9F%20(Merry%20Christmas%20Mr.Lawrence).dsf" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">圣诞快乐,劳伦斯先生《夜》(DSD)</span><span class="cap link footnote">https://alist.thatcdn.cn/Tianyi/Hi-Res/%E7%BA%AF%E9%9F%B3/%E5%9D%82%E6%9C%AC%E9%BE%99%E4%B8%80/%E5%A4%9C%20(nacht)/%E5%9C%A3%E8%AF%9E%E5%BF%AB%E4%B9%90,%20%E5%8A%B3%E4%BC%A6%E6%96%AF%E5%85%88%E7%94%9F%20(Merry%20Christmas%20Mr.Lawrence).dsf</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h2 id="TIPS"><a href="#TIPS" class="headerlink" title="TIPS"></a>TIPS</h2><p>DSD类资源一般都比较大,对播放与监听设备可能要求较高。</p>]]></content>
<summary type="html">坂本龙一电影配乐《战场上的快乐圣诞》OST原声,DSD(DSF)|5.6MHz/1bit</summary>
<category term="分享" scheme="https://blog.thatcoder.cn/categories/%E5%88%86%E4%BA%AB/"/>
<category term="音乐" scheme="https://blog.thatcoder.cn/tags/%E9%9F%B3%E4%B9%90/"/>
</entry>
<entry>
<title>设计模式系列——观察者模式</title>
<link href="https://blog.thatcoder.cn/design/Design-Observer/"/>
<id>https://blog.thatcoder.cn/design/Design-Observer/</id>
<published>2024-06-10T02:01:00.000Z</published>
<updated>2024-08-11T17:49:55.119Z</updated>
<content type="html"><![CDATA[<h2 id="模式"><a href="#模式" class="headerlink" title="模式"></a>模式</h2><blockquote><p>一种订阅机制, 在可观察对象事件发生时通知多个 “观察” 该对象的其他对象。中文以订阅者(观察者)和订阅对象(可观察对象)更容易理解,而发布者理解为统一的通知部门。</p><p>啊〰老师老师,有人就要问了,为什么不用Kafka?Redis?RabbitMQ?<br>没有为什么,Kafka、Redis、RabbitMQ都是消息队列,但观察者模式是一种更加通用的模式,可以用于非使命必达的场景。</p></blockquote><ol><li><strong>发布者</strong> (Publisher):<ul><li>定义:当可观察对象发生变更,筛选对应的订阅者并发布他们关注的内容</li></ul></li><li><strong>订阅者</strong> (Subscriber):<ul><li>定义:除了有<code>update</code>方法,订阅者还需要实现逻辑来处理发布者的通知参数</li></ul></li></ol><h2 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h2><blockquote><p>这个模式的生活场景巨多,就比如 <a href="https://easyf12.top/">一蓑烟雨</a> 的博客就有<a href="https://mailchi.mp/32f0882c1d8e/easyf12">文章订阅</a> 哈哈哈</p></blockquote><ul><li>邮箱订阅:给感兴趣的人推送更新,当然现在不感兴趣也会被迫收到。</li><li>期刊订阅:小学订阅的小学生之友,还有英语老师让大家(可自愿)订阅的英语报。</li><li>菜市场:和老板娘说有漂亮的五花肉记得打电话给我。就是她有时候会忘记。</li><li>群聊通知:排除掉开启了免打扰的成员,剩下的都是订阅者。</li></ul><h2 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h2><h3 id="简单点"><a href="#简单点" class="headerlink" title="简单点"></a>简单点</h3><blockquote><p>一个商品降价订阅通知,商品为小米SU7,为了能在线分享用 TypeScript 写案例分享。</p></blockquote><p>以下代码点击 <a href="https://codesandbox.io/p/devbox/thatcoder-design-qdcpy4?embed=1&file=/src/observer/run.ts">codesandbox</a> 按钮即可运行。<br><a href="https://codesandbox.io/p/devbox/thatcoder-design-qdcpy4?embed=1&file=/src/observer/run.ts"><img src="https://codesandbox.io/static/img/play-codesandbox.svg" alt="Edit ThatCoder-Design"></a></p><h4 id="观察者接口"><a href="#观察者接口" class="headerlink" title="观察者接口"></a>观察者接口</h4><blockquote><p>定义了基本的观察者接口,有观察者的信息和可观察对象的变更回调方法<code>update()</code></p></blockquote><figure class="highlight ts"><figcaption><span>观察者接口</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// Observer.ts 观察者接口</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Observer</span> {<br> <span class="hljs-comment">// 可观察对象变更回调</span><br> <span class="hljs-title function_">update</span>(<span class="hljs-attr">product</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">price</span>: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span>;<br> <span class="hljs-attr">userUUID</span>: <span class="hljs-built_in">string</span>;<br> <span class="hljs-attr">email</span>: <span class="hljs-built_in">string</span>;<br> <span class="hljs-attr">subscriptionType</span>: <span class="hljs-title class_">SubscriptionType</span>;<br> discountThreshold?: <span class="hljs-built_in">number</span>; <span class="hljs-comment">// 仅对 DISCOUNT_TO 类型有效</span><br>}<br><br><span class="hljs-comment">// 订阅类型枚举</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SubscriptionType</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> model: <span class="hljs-built_in">string</span></span>) {}<br><br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> <span class="hljs-variable constant_">IN_STOCK</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SubscriptionType</span>(<span class="hljs-string">"IN_STOCK"</span>);<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> <span class="hljs-variable constant_">DISCOUNT</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SubscriptionType</span>(<span class="hljs-string">"DISCOUNT"</span>);<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> <span class="hljs-variable constant_">DISCOUNT_TO</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SubscriptionType</span>(<span class="hljs-string">"DISCOUNT_TO"</span>);<br><br> <span class="hljs-title function_">getDescription</span>(): <span class="hljs-built_in">string</span> {<br> <span class="hljs-keyword">switch</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">model</span>) {<br> <span class="hljs-keyword">case</span> <span class="hljs-string">"IN_STOCK"</span>:<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"来货通知"</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-string">"DISCOUNT"</span>:<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"降价通知"</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-string">"DISCOUNT_TO"</span>:<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"降价到预期通知"</span>;<br> <span class="hljs-attr">default</span>:<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"未知订阅"</span>;<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="观察者实现"><a href="#观察者实现" class="headerlink" title="观察者实现"></a>观察者实现</h4><blockquote><p>实现了观察者,增加了发送邮箱这个实际的通知方法,在<code>update()</code>实现通知调用</p></blockquote><figure class="highlight ts"><figcaption><span>观察者接口</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// UserObserver.ts 实现具体的观察者,处理不同类型的通知</span><br><span class="hljs-keyword">import</span> {logger} <span class="hljs-keyword">from</span> <span class="hljs-string">"../util/Logger"</span><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Observer</span>, <span class="hljs-title class_">SubscriptionType</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Observer"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserObserver</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Observer</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span><br><span class="hljs-params"> <span class="hljs-keyword">public</span> userUUID: <span class="hljs-built_in">string</span>,</span><br><span class="hljs-params"> <span class="hljs-keyword">public</span> email: <span class="hljs-built_in">string</span>,</span><br><span class="hljs-params"> <span class="hljs-keyword">public</span> subscriptionType: SubscriptionType,</span><br><span class="hljs-params"> <span class="hljs-keyword">public</span> discountThreshold?: <span class="hljs-built_in">number</span> <span class="hljs-comment">// 仅对 DISCOUNT_TO 类型有效</span></span><br><span class="hljs-params"> </span>) {}<br><br> <span class="hljs-title function_">update</span>(<span class="hljs-attr">product</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">price</span>: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {<br> <span class="hljs-keyword">switch</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">subscriptionType</span>) {<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">IN_STOCK</span>:<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">sendEmailNotification</span>(<span class="hljs-string">`<span class="hljs-subst">${product}</span> 来货了!`</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT</span>:<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">sendEmailNotification</span>(<span class="hljs-string">`<span class="hljs-subst">${product}</span> 现在已经降价至 $<span class="hljs-subst">${price}</span>!`</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT_TO</span>:<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">sendEmailNotification</span>(<br> <span class="hljs-string">`<span class="hljs-subst">${product}</span> 现在已经降价至 $<span class="hljs-subst">${price}</span>, 满足您期待的降价 $<span class="hljs-subst">${</span></span><br><span class="hljs-subst"><span class="hljs-string"> <span class="hljs-variable language_">this</span>.discountThreshold ?? <span class="hljs-number">0</span></span></span><br><span class="hljs-subst"><span class="hljs-string"> }</span>% !`</span><br> );<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br><br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">sendEmailNotification</span>(<span class="hljs-attr">message</span>: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {<br> logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">`发送邮件 <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.email}</span>: <span class="hljs-subst">${message}</span>`</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="可观察者接口"><a href="#可观察者接口" class="headerlink" title="可观察者接口"></a>可观察者接口</h4><blockquote><p>定义了基本的可观察者接口,主要有订阅、取消订阅、通知三要素。</p></blockquote><figure class="highlight ts"><figcaption><span>可观察者接口</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// Observable.ts 定义一个可观察对象接口,包括订阅、取消订阅和通知方法</span><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Observer</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"../Observer"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Observable</span> {<br> <span class="hljs-comment">// 订阅</span><br> <span class="hljs-title function_">subscribe</span>(<span class="hljs-attr">observer</span>: <span class="hljs-title class_">Observer</span>): <span class="hljs-built_in">void</span>;<br><br> <span class="hljs-comment">// 取消订阅</span><br> <span class="hljs-title function_">unsubscribe</span>(<span class="hljs-attr">observer</span>: <span class="hljs-title class_">Observer</span>): <span class="hljs-built_in">void</span>;<br><br> <span class="hljs-comment">// 通知</span><br> <span class="hljs-title function_">notifyObservers</span>(): <span class="hljs-built_in">void</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="可观察者实现"><a href="#可观察者实现" class="headerlink" title="可观察者实现"></a>可观察者实现</h4><blockquote><p>实现了一个商品观察对象</p></blockquote><figure class="highlight ts"><figcaption><span>可观察者实现</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// ProductObservable.ts 实现具体的可观察对象(商品通知器)</span><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Observable</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Observable"</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Observer</span>, <span class="hljs-title class_">SubscriptionType</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"../Observer"</span>;<br><span class="hljs-keyword">import</span> { logger } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../util/Logger"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ProductObservable</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Observable</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-attr">publishers</span>: <span class="hljs-title class_">Observer</span>[] = [];<br> <span class="hljs-keyword">private</span> <span class="hljs-attr">currentPrice</span>: <span class="hljs-built_in">number</span> = <span class="hljs-number">0.0</span>;<br> <span class="hljs-keyword">private</span> <span class="hljs-attr">originalPrice</span>: <span class="hljs-built_in">number</span> = <span class="hljs-number">100.0</span>; <span class="hljs-comment">// 原始价格,用于比较</span><br><br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> product: <span class="hljs-built_in">string</span></span>) {<br> logger.<span class="hljs-title function_">info</span>(<br> <span class="hljs-string">`创建可观察对象(商品:<span class="hljs-subst">${product}</span>),价格 $<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.originalPrice}</span>`</span><br> );<br> }<br><br> <span class="hljs-title function_">subscribe</span>(<span class="hljs-attr">publisher</span>: <span class="hljs-title class_">Observer</span>): <span class="hljs-built_in">void</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">publishers</span>.<span class="hljs-title function_">push</span>(publisher);<br> logger.<span class="hljs-title function_">info</span>(<br> <span class="hljs-string">`用户UUID: <span class="hljs-subst">${publisher.userUUID}</span> ,成功订阅商品 <span class="hljs-subst">${</span></span><br><span class="hljs-subst"><span class="hljs-string"> <span class="hljs-variable language_">this</span>.product</span></span><br><span class="hljs-subst"><span class="hljs-string"> }</span> ,订阅类型 <span class="hljs-subst">${publisher.subscriptionType.getDescription()}</span>.`</span><br> );<br> }<br><br> <span class="hljs-title function_">unsubscribe</span>(<span class="hljs-attr">publisher</span>: <span class="hljs-title class_">Observer</span>): <span class="hljs-built_in">void</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">publishers</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">publishers</span>.<span class="hljs-title function_">filter</span>(<br> <span class="hljs-function">(<span class="hljs-params">obs</span>) =></span> obs.<span class="hljs-property">userUUID</span> !== publisher.<span class="hljs-property">userUUID</span><br> );<br> logger.<span class="hljs-title function_">info</span>(<br> <span class="hljs-string">`用户UUID: <span class="hljs-subst">${publisher.userUUID}</span> ,取消订阅商品 <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.product}</span> `</span><br> );<br> }<br><br> <span class="hljs-title function_">notifyObservers</span>(): <span class="hljs-built_in">void</span> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> publisher <span class="hljs-keyword">of</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">publishers</span>) {<br> <span class="hljs-keyword">switch</span> (publisher.<span class="hljs-property">subscriptionType</span>) {<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">IN_STOCK</span>:<br> publisher.<span class="hljs-title function_">update</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">product</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span>);<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT</span>:<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span> < <span class="hljs-variable language_">this</span>.<span class="hljs-property">originalPrice</span>) {<br> publisher.<span class="hljs-title function_">update</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">product</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span>);<br> }<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT_TO</span>:<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span> <= (publisher.<span class="hljs-property">discountThreshold</span> ?? <span class="hljs-number">0</span>)) {<br> publisher.<span class="hljs-title function_">update</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">product</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span>);<br> }<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> }<br> <br> <span class="hljs-title function_">productRestocked</span>(): <span class="hljs-built_in">void</span> {<br> logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">`商品 <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.product}</span> 采购成功`</span>);<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">notifyObservers</span>();<br> }<br><br> <span class="hljs-title function_">productDiscounted</span>(<span class="hljs-attr">newPrice</span>: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">currentPrice</span> = newPrice;<br> <span class="hljs-keyword">if</span> (newPrice === <span class="hljs-variable language_">this</span>.<span class="hljs-property">originalPrice</span>) {<br> logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">`商品 <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.product}</span> 恢复原价`</span>);<br> } <span class="hljs-keyword">else</span> {<br> logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">`商品 <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.product}</span> 降价至: $<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.currentPrice}</span>`</span>);<br> }<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">notifyObservers</span>();<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="测试效果"><a href="#测试效果" class="headerlink" title="测试效果"></a>测试效果</h4><blockquote><p>创建 小米SU7 这个可观察对象<br>三个用户关注了 小米SU7,关注类型不一样<br>在 小米SU7 库存和价格变动时候可以观测到对应的通知变化</p></blockquote><figure class="highlight ts"><figcaption><span>测试</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// main.ts</span><br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">ProductObservable</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./observable/ProductObservable"</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">UserObserver</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./UserObserver"</span>;<br><span class="hljs-keyword">import</span> { <span class="hljs-title class_">SubscriptionType</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Observer"</span>;<br><span class="hljs-keyword">import</span> { logger } <span class="hljs-keyword">from</span> <span class="hljs-string">"../util/Logger"</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">TestObserver</span> = (<span class="hljs-params"></span>) => {<br> <span class="hljs-comment">// 创建可观察对象(商品通知器)</span><br> <span class="hljs-keyword">const</span> su7Notifier = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ProductObservable</span>(<span class="hljs-string">"小米SU7"</span>);<br><br> <span class="hljs-comment">// 创建观察者(用户)</span><br> <span class="hljs-keyword">const</span> user1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserObserver</span>(<br> <span class="hljs-string">"UUID-1111"</span>,<br> <span class="hljs-string">"[email protected]"</span>,<br> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">IN_STOCK</span><br> );<br> <span class="hljs-keyword">const</span> user2 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserObserver</span>(<br> <span class="hljs-string">"UUID-2222"</span>,<br> <span class="hljs-string">"[email protected]"</span>,<br> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT</span><br> );<br> <span class="hljs-keyword">const</span> user3 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserObserver</span>(<br> <span class="hljs-string">"UUID-3333"</span>,<br> <span class="hljs-string">"[email protected]"</span>,<br> <span class="hljs-title class_">SubscriptionType</span>.<span class="hljs-property">DISCOUNT_TO</span>,<br> <span class="hljs-number">50</span><br> );<br><br> <span class="hljs-comment">// 用户1订阅iPhone 15有货通知</span><br> su7Notifier.<span class="hljs-title function_">subscribe</span>(user1);<br> <span class="hljs-comment">// 用户2订阅iPhone 15降价通知</span><br> su7Notifier.<span class="hljs-title function_">subscribe</span>(user2);<br> <span class="hljs-comment">// 用户3订阅iPhone 15降价到50%通知</span><br> su7Notifier.<span class="hljs-title function_">subscribe</span>(user3);<br><br> <span class="hljs-comment">// 商品到货,通知相关用户</span><br> su7Notifier.<span class="hljs-title function_">productRestocked</span>();<br><br> <span class="hljs-comment">// 商品降价,通知相关用户</span><br> su7Notifier.<span class="hljs-title function_">productDiscounted</span>(<span class="hljs-number">60.0</span>);<br><br> <span class="hljs-comment">// 商品恢复原价</span><br> su7Notifier.<span class="hljs-title function_">productDiscounted</span>(<span class="hljs-number">100.0</span>);<br><br> <span class="hljs-comment">// 商品降价到50%,通知相关用户</span><br> su7Notifier.<span class="hljs-title function_">productDiscounted</span>(<span class="hljs-number">45.0</span>);<br><br> <span class="hljs-comment">// 用户1取消iPhone 15的订阅</span><br> su7Notifier.<span class="hljs-title function_">unsubscribe</span>(user1);<br><br> <span class="hljs-comment">// 商品到货,通知剩余的用户</span><br> su7Notifier.<span class="hljs-title function_">productRestocked</span>();<br>};<br></code></pre></td></tr></table></figure><h4 id="测试结果"><a href="#测试结果" class="headerlink" title="测试结果"></a>测试结果</h4><p>和预想一致,可观察对象只需要关注自己的变动就可以了,用户考虑的就多了(还要点击订阅)。<br>降价到60,所以用户3不被通知<br>用户1取消订阅,所以来货了也不被通知<br>当然这是最简单的示例</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240811190729.png" alt="运行结果" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">运行结果</span></div></div><h3 id="Spring监听机制"><a href="#Spring监听机制" class="headerlink" title="Spring监听机制"></a>Spring监听机制</h3><p>Spring有<code>EventListener</code>类似去定义一个事件的处理逻辑,相当于在里面写了订阅者的通知方法。<code>ApplicationEventPublisher</code>会去发布定义的事件,相当于可观察者的对象发生了变动。不同的是我们只关心发布和处理逻辑即可,中间的调用交给了<code>Listener</code>。</p><h4 id="生命周期事件"><a href="#生命周期事件" class="headerlink" title="生命周期事件"></a>生命周期事件</h4><p>在包 <a href="https://github.com/spring-projects/spring-framework/tree/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-context/src/main/java/org/springframework/context/event">org.springframework.context.event</a> 下面有很多与 <code>ApplicationContext</code> 生命周期相关的事件,这些事件都继承自 <code>ApplicationContextEvent</code>,包括 <code>ContextRefreshedEvent</code>, <code>ContextStartedEvent</code>, <code>ContextStoppedEvent</code>, <code>ContextClosedEvent</code>。<br>到了对应的生命周期会调用订阅。</p><figure class="highlight kotlin"><figcaption><span>启动和刷新</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.context.ApplicationListener<br><span class="hljs-keyword">import</span> org.springframework.context.event.ContextRefreshedEvent <br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component <br> <br><span class="hljs-meta">@Component</span> <br><span class="hljs-keyword">class</span> <span class="hljs-title class_">StartupListener</span> : <span class="hljs-type">ApplicationListener</span><<span class="hljs-type">ContextRefreshedEvent</span>> { <br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onApplicationEvent</span><span class="hljs-params">(event: <span class="hljs-type">ContextRefreshedEvent</span>)</span></span> { <br> println(<span class="hljs-string">"应用刷新成功!"</span>) <br> } <br>}<br></code></pre></td></tr></table></figure><h4 id="事务监听"><a href="#事务监听" class="headerlink" title="事务监听"></a>事务监听</h4><blockquote><p>@TransactionalEventListener<br>举例一个下单成功后的发布事务</p></blockquote><figure class="highlight kotlin"><figcaption><span>事件定义</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">data</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderPlacedEvent</span>(<span class="hljs-keyword">val</span> orderId: String, <span class="hljs-keyword">val</span> userEmail: String)<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>事件处理</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.context.event.TransactionalEventListener<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component<br><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderPlacedEventListener</span> {<br><br> <span class="hljs-meta">@TransactionalEventListener</span><br> <span class="hljs-meta">@Async</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleOrderPlacedEvent</span><span class="hljs-params">(event: <span class="hljs-type">OrderPlacedEvent</span>)</span></span> {<br> <span class="hljs-comment">// 发送订单确认邮件</span><br> <span class="hljs-keyword">val</span> orderId = event.orderId<br> <span class="hljs-keyword">val</span> userEmail = event.userEmail<br> println(<span class="hljs-string">"发送 <span class="hljs-variable">$orderId</span> 信息到用户邮箱 <span class="hljs-variable">$userEmail</span>"</span>)<br> <span class="hljs-comment">// 实际发送邮件的逻辑...</span><br> }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>事件触发</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.context.ApplicationEventPublisher<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Service<br><span class="hljs-keyword">import</span> org.springframework.transaction.<span class="hljs-keyword">annotation</span>.Transactional<br><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderService</span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> eventPublisher: ApplicationEventPublisher) {<br><br> <span class="hljs-meta">@Transactional</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">save</span><span class="hljs-params">(order: <span class="hljs-type">Order</span>)</span></span> {<br> <span class="hljs-comment">// 处理下单逻辑...</span><br> <span class="hljs-comment">// 发布事件</span><br> eventPublisher.publishEvent(OrderPlacedEvent(orderId, userEmail))<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li><strong>代码解耦</strong>:观察者和订阅者的逻辑分开,订阅者只引用了抽象的发布者接口,每个可观察者只需要关注自己的实现。</li><li><strong>抽象耦合</strong>:如上代码解耦后逻辑上依然保持着抽象的耦合,订阅者只需要注册订阅即可</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li><strong>隐式依赖</strong>:抽象耦合就代表着事件通知机制是隐式的,系统的行为可能变得难以预测和理解。及时补充文档,不然就慢慢DEBUG。</li><li><strong>瞬时峰值</strong>:某个可观察对象有大量订阅时,触发<code>update</code>带来的巨额性能开销可能会导致性能瓶颈,甚至系统阻塞。注意异步和削峰。</li><li><strong>并发问题</strong>:多线程中,事件的发布和订阅者的变动可能带来并发问题。需要复杂的同步机制来确保线程安全,比如<code>ConcurrentModificationException</code>。除了线程安全的集合可能还需要考虑显式锁、读写锁或原子操作。</li></ul><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/77412f61-b3f4-4a0a-8a2d-acf82821291c.jpeg" alt="IDEA的监听耳机" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">IDEA的监听耳机</span></div></div>]]></content>
<summary type="html">观察者模式定义对象间一对多依赖关系,使得一对象状态变化时通知并更新其依赖对象进行处理。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="设计模式" scheme="https://blog.thatcoder.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="编程艺术" scheme="https://blog.thatcoder.cn/tags/%E7%BC%96%E7%A8%8B%E8%89%BA%E6%9C%AF/"/>
</entry>
<entry>
<title>设计模式系列——责任链模式</title>
<link href="https://blog.thatcoder.cn/design/Design-Chain-of-Responsibility/"/>
<id>https://blog.thatcoder.cn/design/Design-Chain-of-Responsibility/</id>
<published>2024-06-10T02:00:00.000Z</published>
<updated>2024-08-11T08:20:26.008Z</updated>
<content type="html"><![CDATA[<p>开个坑补齐设计模式系列笔记, 顺带回顾Spring源码中的设计模式运用…</p><h2 id="模式"><a href="#模式" class="headerlink" title="模式"></a>模式</h2><blockquote><p>责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许多个对象有机会处理同一个请求,从而避免请求的发送者与多个接收者之间的耦合, 通常用于请求有多个阶段。该模式通过将这些对象连成一条链,并沿着链传递请求,直到有一个对象处理请求为止。如果链的末端没有对象处理请求,整个请求将被丢弃或默认处理。</p></blockquote><p><strong>未加粗的是责任链复杂程度上去之后可选的角色</strong></p><ol><li><strong>抽象处理者(Handler)</strong>:<ul><li><strong>定义</strong>:一个接口或抽象类,通常包含一个方法来处理请求以及一个方法来设置下一个处理者。它规定了所有具体处理者都必须实现的基本操作,如处理请求或将请求传递给链中的下一个处理者。</li><li><strong>角色</strong>:<strong>接口或抽象类</strong> 作为处理链的核心定义,保证每个处理者都具有处理请求的能力。</li></ul></li></ol><ul><li>基础处理者 (Base Handler): 可选<ul><li><strong>定义</strong>:一个接口或部分实现类,它实现了抽象处理者的部分功能或是抽象处理者的拓展。</li><li><strong>角色</strong>:<strong>复用和扩展</strong> 提供公共的逻辑实现,减少子类的重复代码,增强代码的可复用性。</li></ul></li></ul><ol start="2"><li><strong>具体处理者(ConcreteHandlers)</strong>:<ul><li><strong>定义</strong>:每个实现类,实现了处理某种请求的逻辑。当它无法处理请求时,会将请求传递给链中的下一个处理者。</li><li><strong>角色</strong>:<strong>链的节点实现</strong> 实现自己的处理逻辑来参与责任链的请求处理。</li></ul></li></ol><ul><li>链的管理者(Chain Manager):可选<ol><li>定义:通常是一个Chain类,实现创建和管理处理者的顺序、组装责任链,并维护链的整体状态。</li><li>角色:<strong>构建和维护责任链</strong> 确保处理者按照正确的顺序处理请求,同时可以动态地添加、移除或调整链中的处理者。</li></ol></li><li>链的构造器(Chain Creator):可选<ol><li>定义:通常是一个工厂类或构造器,负责初始化责任链的结构并返回链的起始处理者。它可以根据具体的业务需求,创建不同类型的责任链。</li><li>角色:<strong>生成责任链实例</strong> 确保处理者按照正确的顺序处理请求,同时可以动态地添加、移除或调整链中的处理者。</li></ol></li></ul><ol start="3"><li><strong>客户类(Client)</strong>:<ul><li><strong>定义</strong>:客户类是发起请求的对象,它通常会自行或调用链的构造器创建并设置责任链,然后向责任链的第一个处理者提交请求。</li><li><strong>角色</strong>:<strong>责任链的创建者</strong> <strong>请求的发起者</strong></li></ul></li></ol><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/547ba1ae581c65781d4575b7b5792b0c.png" alt="责任链模式结构图" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">责任链模式结构图</span></div></div><h2 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h2><p>公司报销流程就像一条责任链,每个环节都根据条件决定是否继续处理:</p><ol><li><strong>填写申请</strong>:你提交的报销单是链上的第一个环节,检查所有必要的字段和金额,确保格式正确。如果不符合条件,它会被退回。</li><li><strong>审批环节</strong>:提交后的申请进入审批环节(链路)。每一级上司根据公司政策比如费用上限或是否有票据判断是否继续处理。如果符合条件或该审批者无权审批,申请会被传递到下一个环节。</li><li><strong>财务审核</strong>:通过所有审批后,财务部门再进行一次核查,确保链路都是<code>true</code>且符合财务规定。如果审核通过,申请继续流向资金发放环节 (实际的处理方法)。</li><li><strong>资金发放</strong>:经过所有环节的条件检查和批准,资金会发放到你的账户中。</li></ol><p>每个环节都有自己的条件判断,每个节点只处理自己能处理的部分,不符合条件的节点将被跳过,确保整个流程高效顺畅。</p><h2 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h2><h3 id="简单点"><a href="#简单点" class="headerlink" title="简单点"></a>简单点</h3><blockquote><p>先来个简单点的,虚拟一个简单的过滤器链实现和实际的使用,用来处理HTTP请求。</p></blockquote><h4 id="抽象处理者"><a href="#抽象处理者" class="headerlink" title="抽象处理者"></a>抽象处理者</h4><figure class="highlight kotlin"><figcaption><span>接口定义</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Handler</span> : <span class="hljs-type">Comparable</span><<span class="hljs-type">Handler</span>> {<br> <span class="hljs-keyword">var</span> index: <span class="hljs-built_in">Int</span> <span class="hljs-comment">// 责任链顺序</span><br><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setNext</span><span class="hljs-params">(handler: <span class="hljs-type">Handler</span>)</span></span>: Handler <span class="hljs-comment">// 下一个</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleRequest</span><span class="hljs-params">(request: <span class="hljs-type">Request</span>)</span></span> <span class="hljs-comment">// 处理方法,处理同一种入参</span><br><br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">compareTo</span><span class="hljs-params">(other: <span class="hljs-type">Handler</span>)</span></span>: <span class="hljs-built_in">Int</span> = <span class="hljs-keyword">this</span>.index - other.index<br>}<br></code></pre></td></tr></table></figure><h4 id="基础处理者"><a href="#基础处理者" class="headerlink" title="基础处理者"></a>基础处理者</h4><figure class="highlight kotlin"><figcaption><span>基础实现</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 基础处理者,提供处理链传递逻辑,并实现index</span><br><span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">BaseHandler</span>(<span class="hljs-keyword">override</span> <span class="hljs-keyword">var</span> index: <span class="hljs-built_in">Int</span>) : Handler {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> nextHandler: Handler? = <span class="hljs-literal">null</span><br><br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setNext</span><span class="hljs-params">(handler: <span class="hljs-type">Handler</span>)</span></span>: Handler {<br> nextHandler = handler<br> <span class="hljs-keyword">return</span> handler<br> }<br><br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleRequest</span><span class="hljs-params">(request: <span class="hljs-type">Request</span>)</span></span> {<br> nextHandler?.handleRequest(request)<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="具体处理者"><a href="#具体处理者" class="headerlink" title="具体处理者"></a>具体处理者</h4><figure class="highlight kotlin"><figcaption><span>具体实现</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 具体处理者1:处理认证逻辑</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AuthenticationHandler</span>(index: <span class="hljs-built_in">Int</span>) : BaseHandler(index) {<br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleRequest</span><span class="hljs-params">(request: <span class="hljs-type">Request</span>)</span></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-string">"ROLE_ADMIN"</span> == request?.role) {<br> <span class="hljs-keyword">super</span>.handleRequest(request)<br> } <span class="hljs-keyword">else</span> {<br> response.WriteError(<span class="hljs-string">"认证失败"</span>)<br> }<br> }<br>}<br><br><span class="hljs-comment">// 具体处理者2:处理日志记录逻辑</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">LoggingHandler</span>(index: <span class="hljs-built_in">Int</span>) : BaseHandler(index) {<br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleRequest</span><span class="hljs-params">(request: <span class="hljs-type">Request</span>)</span></span> {<br> Logger.info(<span class="hljs-string">"登录:<span class="hljs-subst">${request.username}</span>"</span>)<br> <span class="hljs-keyword">super</span>.handleRequest(request)<br> }<br>}<br><br></code></pre></td></tr></table></figure><h4 id="链管理者"><a href="#链管理者" class="headerlink" title="链管理者"></a>链管理者</h4><figure class="highlight kotlin"><figcaption><span>链管理者</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 链的管理者:负责组装和管理责任链</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ChainManager</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> handlers: MutableList<Handler> = mutableListOf()<br> <span class="hljs-comment">// 添加处理器到链中,指定顺序</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">addHandler</span><span class="hljs-params">(handler: <span class="hljs-type">Handler</span>)</span></span>: ChainManager {<br> handlers.add(handler)<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span><br> }<br> <span class="hljs-comment">// 从链中移除处理器</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">removeHandler</span><span class="hljs-params">(handler: <span class="hljs-type">Handler</span>)</span></span>: ChainManager {<br> handlers.remove(handler)<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span><br> }<br> <span class="hljs-comment">// 构建责任链</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">buildChain</span><span class="hljs-params">()</span></span>: Handler? {<br> <span class="hljs-keyword">if</span> (handlers.isEmpty()) {<br> <span class="hljs-keyword">throw</span> IllegalArgumentException(<span class="hljs-string">"空链"</span>)<br> }<br> <span class="hljs-comment">// 根据index字段排序</span><br> handlers.sort()<br> <span class="hljs-comment">// 链接处理器</span><br> <span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span> until handlers.size - <span class="hljs-number">1</span>) {<br> handlers[i].setNext(handlers[i + <span class="hljs-number">1</span>])<br> }<br> <span class="hljs-keyword">return</span> handlers.firstOrNull()<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="链构建者"><a href="#链构建者" class="headerlink" title="链构建者"></a>链构建者</h4><figure class="highlight kotlin"><figcaption><span>链构建者</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 链的创建者:工厂类,用于创建责任链</span><br><span class="hljs-keyword">object</span> ChainCreator {<br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">createDefaultChain</span><span class="hljs-params">()</span></span>: Handler? {<br> <span class="hljs-keyword">val</span> manager = ChainManager()<br> manager.addHandler(LoggingHandler(<span class="hljs-number">2</span>)) <span class="hljs-comment">// 指定顺序</span><br> .addHandler(AuthenticationHandler(<span class="hljs-number">1</span>))<br> <span class="hljs-keyword">return</span> manager.buildChain()<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><blockquote><p>这里假设将我们虚拟的责任链加入到请求拦截器,真实这里应该是基于<code>HandlerInterceptor</code>实现的处理者。</p></blockquote><figure class="highlight kotlin"><figcaption><span>run</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">WebConfig</span> : <span class="hljs-type">WebMvcConfigurer</span> {<br><br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">addInterceptors</span><span class="hljs-params">(registry: <span class="hljs-type">InterceptorRegistry</span>)</span></span> {<br> <span class="hljs-comment">// 创建责任链</span><br> <span class="hljs-keyword">val</span> chain = ChainCreator.createDefaultChain()<br> <br> <span class="hljs-comment">// 注册责任链的第一个处理者</span><br> registry.addInterceptor(chain)<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="Spring-Web"><a href="#Spring-Web" class="headerlink" title="Spring Web"></a>Spring Web</h3><blockquote><p>在 Spring Web 框架中,责任链模式被广泛应用于请求处理的各个阶段,尤其是在处理请求拦截和映射时。</p></blockquote><p>看 Spring Web 案例代码之前,现梳理一遍请求到链路末端处理到返回的逻辑,比如启动并访问 <code>OrderController </code>的 <code>@GetMapping("/order/list") </code>的 <code>public ResponseEntity<ResponseRow> list() </code>方法。</p><ol><li>Spring Boot 启动时,<code>SpringApplication.run()</code> 方法会被调用。这一方法负责引导和启动 Spring 应用程序上下文,加载所有的配置类和 Bean。</li><li>理所当然 <a href="https://github.com/spring-projects/spring-framework/blob/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java">DispatcherServlet.java</a> 作为核心组件会被通过自动配置机制自动注册并初始化,当作 Spring MVC 的前端控制器,用于接收和处理所有的 HTTP 请求。</li><li><code>DispatcherServlet</code> 初始化时会调用 <a href="https://github.com/spring-projects/spring-framework/blob/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java#L600-L621">initHandlerMappings()</a> 方法来加载所有的<code>HandlerMapping</code> 实现类储存到私有属性 <code>private List<HandlerMapping> handlerMappings</code>,并且等待处理即将到来的请求。</li><li><a href="https://github.com/spring-projects/spring-framework/blob/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java">HandlerMapping</a> 类负责将请求映射到适当的处理器(例如 <code>@Controller</code> 中的方法)。</li><li>用户发出 <code>/order/list</code> 请求时,<code>DispatcherServlet</code> 作为前端控制器接收 HTTP 请求,在 <a href="https://github.com/spring-projects/spring-framework/blob/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java#L1048-L1129">doDispatch()</a> 方法中遍历 <code>handlerMappings</code> 列表并调用每个 <code>HandlerMapping</code> 实现类的 <code>getHandler()</code> 方法,尝试找到合适的 <code>HandlerExecutionChain</code> 来处理这个请求。</li><li>我们举例是 <code>@GetMapping("/order/list") </code>所以会用<a href="https://github.com/spring-projects/spring-framework/blob/bf5e218b3580df20e6df129eadead4721e1f4f03/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java">RequestMappingHandlerMapping</a>识别出 <code>OrderController</code> 中相应的处理方法,并返回一个 <code>HandlerExecutionChain</code>。这个 <code>HandlerExecutionChain</code> 包含了处理该请求的控制器方法(如 <code>OrderController.list()</code>)以及相关的拦截器链。</li><li>当然拦截器(多个 <code>HandlerInterceptor</code> 以及可能是异步的 <code>AsyncHandlerInterceptor</code> 和 <code>WebRequestHandlerInterceptorAdapter</code> )可能会执行一些通用逻辑,如权限验证、日志记录等,都是按既定顺序执行。如果所有的拦截器都允许请求通过,将调用实际的处理器方法(如 <code>OrderController.list()</code>)。</li><li>调用实际的处理器方法后还会继续调用拦截器的 <code>postHandle()</code> 方法,在生成响应之前对结果进一步处理。最后,无论请求是否成功,<code>afterCompletion()</code> 方法都会被调用,此为清理操作。</li></ol><h4 id="关系图"><a href="#关系图" class="headerlink" title="关系图"></a>关系图</h4><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240810215423.png" alt="DispatcherServlet.java" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">DispatcherServlet.java</span></div></div> <div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240810215605.png" alt="RequestMappingHandlerMapping.java" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">RequestMappingHandlerMapping.java</span></div></div> <div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240810214830.png" alt="抽象处理者、基础处理者、具体处理者" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">抽象处理者、基础处理者、具体处理者</span></div></div><h4 id="HandlerInterceptor"><a href="#HandlerInterceptor" class="headerlink" title="HandlerInterceptor"></a>HandlerInterceptor</h4><blockquote><p><strong>抽象处理者</strong> 网络请求处理的不同阶段拦截器</p></blockquote><pre><code class="java">public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }//调用"Controller"方法之前 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { }//调用"Controller"方法之后渲染"ModelAndView"之前 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }//渲染"ModelAndView"之后 } </code></pre><h4 id="AsyncHandlerInterceptor"><a href="#AsyncHandlerInterceptor" class="headerlink" title="AsyncHandlerInterceptor"></a>AsyncHandlerInterceptor</h4><blockquote><p><strong>基础处理者</strong> 继承了 <code>HandlerInterceptor</code> 接口,并添加了处理异步请求的方法。</p></blockquote><pre><code class="java">public interface AsyncHandlerInterceptor extends HandlerInterceptor { default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } } </code></pre><h4 id="WebRequestHandlerInterceptorAdapter"><a href="#WebRequestHandlerInterceptorAdapter" class="headerlink" title="WebRequestHandlerInterceptorAdapter"></a>WebRequestHandlerInterceptorAdapter</h4><blockquote><p><strong>具体处理者</strong> 实现了 <code>AsyncHandlerInterceptor</code> 接口。并将 <code>WebRequestInterceptor</code> 转换为 <code>HandlerInterceptor</code>。它将 <code>WebRequestInterceptor</code> 的处理逻辑适配为 HTTP 请求处理逻辑,并处理异步请求的逻辑。</p></blockquote><pre><code class="java">public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerInterceptor { private final WebRequestInterceptor requestInterceptor; public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor) { Assert.notNull(requestInterceptor, "WebRequestInterceptor must not be null"); this.requestInterceptor = requestInterceptor; } public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { this.requestInterceptor.preHandle(new DispatcherServletWebRequest(request, response)); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { this.requestInterceptor.postHandle(new DispatcherServletWebRequest(request, response), modelAndView != null && !modelAndView.wasCleared() ? modelAndView.getModelMap() : null); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { this.requestInterceptor.afterCompletion(new DispatcherServletWebRequest(request, response), ex); } public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) { WebRequestInterceptor var5 = this.requestInterceptor; if (var5 instanceof AsyncWebRequestInterceptor asyncInterceptor) { DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response); asyncInterceptor.afterConcurrentHandlingStarted(webRequest); } } } </code></pre><h4 id="DispatcherServlet"><a href="#DispatcherServlet" class="headerlink" title="DispatcherServlet"></a>DispatcherServlet</h4><blockquote><p><strong>客户端</strong> 该调度程序负责配置和执行拦截器链。Spring MVC 的前端控制器,用于接收和处理所有的 HTTP 请求。</p><p>这条链路客户端到处理器中间其实还有两个部分,稍后就有介绍<br><strong>HandlerMapping</strong>:用于查找与请求匹配的处理器(<code>Controller</code>),并返回<code>HandlerExecutionChain</code>。<br><strong>HandlerExecutionChain</strong>:包含处理器和一系列拦截器(处理器)(<code>HandlerInterceptor</code>)。它负责在请求处理过程中依次调用链中的拦截器。</p></blockquote><pre><code class="java">public class DispatcherServlet extends FrameworkServlet { ... protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerExecutionChain mappedHandler = null; ... // 获取处理链 mappedHandler = this.getHandler(processedRequest); ... } ... @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { Iterator var2 = this.handlerMappings.iterator(); while(var2.hasNext()) { HandlerMapping mapping = (HandlerMapping)var2.next(); HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; } ...} </code></pre><h4 id="HandlerExecutionChain"><a href="#HandlerExecutionChain" class="headerlink" title="HandlerExecutionChain"></a>HandlerExecutionChain</h4><blockquote><p><strong>处理链管理者</strong> Spring Web中需要一个角色,将处理器和拦截器链组织在一起,按照顺序处理请求。</p><p>它是一个管理处理器和拦截器链的组件。在处理请求时,它负责管理和调用所有的拦截器方法,并最终调用实际的处理器(例如 Controller)。</p></blockquote><h4 id="HandlerMapping"><a href="#HandlerMapping" class="headerlink" title="HandlerMapping"></a>HandlerMapping</h4><blockquote><p><strong>链的建立者</strong> Spring Web中需要一个角色,负责从请求中确定合适的处理器,并将处理器与拦截器链组合起来,形成一个完整的处理链(也就是<code>HandlerExecutionChain</code>)。</p><p>它可以被视为该条责任链的启动点或“链的建立者”。它确定了请求将由哪个处理器处理,并且可能为处理器配置了一些拦截器。</p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li><strong>业务解耦</strong>:每个节点独立可以随时新增或删除,不仅不会影响事务请求的业务代码,还不会影响其它责任节点。</li><li><strong>责任单一</strong>:责任链每个节点处理的对象是同一种,但是每个节点处理的事务不一样。</li><li><strong>动态组合</strong>:节点之间有强秩序,但是可以根据不同业务动态重新组合成一个新链路。</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li><strong>性能缺陷</strong>:节点之间有强秩序,大概率会走完全部链路,必影响耗时。</li><li><strong>死循环</strong>:链路过长开发人员不熟悉整个流程一样难以新增节点,容易出现链路闭环。即新增的F节点可能下一步需要B, 但是要在C之前,便出现 B->D->C->F->B。当然这个问题在开发阶段能处理。</li></ul>]]></content>
<summary type="html">责任链模式通过链条传递请求,简化责任分配,增强系统灵活性。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="设计模式" scheme="https://blog.thatcoder.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="编程艺术" scheme="https://blog.thatcoder.cn/tags/%E7%BC%96%E7%A8%8B%E8%89%BA%E6%9C%AF/"/>
</entry>
<entry>
<title>Tabby的Web前端与Gateway网关部署</title>
<link href="https://blog.thatcoder.cn/tabby-web-gateway/"/>
<id>https://blog.thatcoder.cn/tabby-web-gateway/</id>
<published>2024-05-13T02:00:00.000Z</published>
<updated>2024-05-13T04:44:56.570Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>经常换设备用终端时候总是要下载 <code>tabby</code> 和添加原来的连接配置,还不同步。一直想搭建官方提供的<code>tabby-web</code>,现在终于有空搞。<br>搭建完发现不只是可以同步,还可以在网页连接配置里的终端,但是要搭建网关,顺便把网关也搭建了。web是http协议,网关是ws协议。<br>但是搭建过程和官方REDEME相比差别甚大 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_tiaokan.png"/></span> 遂本文记载 <code>tabby-web</code> 与 <code>tabby-gateway</code> 的搭建配置与联动。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/e97c6c33f5dc5124da4eff1a818b454c.png" alt="部署结果" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">部署结果</span></div></div><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul><li>docker!docker!docker!</li><li>两份域名证书(前端和网关,前端的自己反向代理使用,网关的需要挂载到容器)</li></ul><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><blockquote><p>先部署吧,碰到的小毛病都是部署后面的(只部署 tabby-web 的话自己删一下网关那段)<br>一起整合到了编排文件。</p></blockquote><p>需要修改的地方如下,分是否开启SSL两种:</p><ul><li>开启SSL<ol><li>填写 网关证书目录地址:签名和私钥命名为gateway.pem和gateway.key</li><li>填写 前端容器挂载目录:后面要用到。</li><li>填写 两个映射的端口</li><li>填写 前端域名</li><li>填写 网关密钥(相当于自定义的密码)</li><li>填写 SOCIAL_AUTH_GITHUB_KEY 与 SOCIAL_AUTH_GITHUB_SECRET (用于用户Github登录,<a href="https://github.com/settings/developers">点进来进来创建一个</a>,回调地址填前端域名)</li></ol></li><li>取消SSL<ol><li>修改 tabby-gateway.command: –token-auth –host 0.0.0.0</li><li>删除 tabby-gateway.volumes</li><li>修改 “网关端口:443” -> “网关端口:9000”</li><li>填写 网关密钥</li><li>填写 前端域名</li><li>填写 SOCIAL_AUTH_GITHUB_KEY 与 SOCIAL_AUTH_GITHUB_SECRET</li></ol></li></ul><figure class="highlight yaml"><figcaption><span>docker-compose.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.0"</span><br><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">tabby-gateway:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/eugeny/tabby-connection-gateway:master</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">"tabby-gateway"</span><br> <span class="hljs-attr">command:</span> <span class="hljs-string">--token-auth</span> <span class="hljs-string">--host</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-string">--port</span> <span class="hljs-number">443</span> <span class="hljs-string">--certificate</span> <span class="hljs-string">/custom/ssl/gateway.pem</span> <span class="hljs-string">--private-key</span> <span class="hljs-string">/custom/ssl/gateway.key</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">TABBY_AUTH_TOKEN=网关密钥</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"网关端口:443"</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/你的网关证书地址:/custom/ssl</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br><br> <span class="hljs-attr">tabby-web:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/eugeny/tabby-web:latest</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">tabby-web</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/你的前端容器挂载目录:/data</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">DATABASE_URL=sqlite:////data/db.sqlite3</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">DEBUG=False</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">PORT=8080</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">APP_DIST_STORAGE=file:///data</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">SOCIAL_AUTH_GITHUB_KEY=记得填</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">SOCIAL_AUTH_GITHUB_SECRET=记得填</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">com.centurylinklabs.watchtower.enable=true</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.app-tabby-web.tls=true</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.app-tabby-web.tls.certresolver=cloudflare</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.app-tabby-web.entrypoints=websecure</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.app-tabby-web.rule=Host(`前端域名`)</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.app-tabby-web.service=app-tabby-web</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.app-tabby-web.loadbalancer.server.port=9090</span><br> <span class="hljs-attr">logging:</span><br> <span class="hljs-attr">driver:</span> <span class="hljs-string">json-file</span><br> <span class="hljs-attr">options:</span><br> <span class="hljs-attr">max-size:</span> <span class="hljs-string">5m</span><br> <span class="hljs-attr">max-file:</span> <span class="hljs-string">"5"</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"前端端口:8080"</span><br></code></pre></td></tr></table></figure><blockquote><p>编排就交给你了,部署完接下来分两步,先跑通web后讲gateway。</p></blockquote><h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240513122813.png" alt="前端警告⚠️" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">前端警告⚠️</span></div></div><p>访问前端后会肯定有一个提醒,我们来到tabby-web容器的命令行执行如下</p><figure class="highlight bash"><figcaption><span>web容器</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">/manage.sh add_version 1.0.187-nightly.1<br></code></pre></td></tr></table></figure><p><a href="#%E8%B0%83%E6%95%B4%E4%BE%9D%E8%B5%96">下面是毛病吐槽,点击跳过碎碎念。</a></p><p>最新版其实是 <code>1.0.197-nightly.1</code> ,但是容易暴毙。<br>安装完之后重启容器发现仍然是黑屏! <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_think.png"/></span></p><p>打开F12网络检查会发现找不到 <code>/app-dist/1.0.187-nightly.1/</code> 下面的文件。<br>来到服务器前端容器挂载目录会发现有<code>1.0.187-nightly.1</code>目录,但是它的子目录是<code>/tmpxxxx</code>,而<code>/tmpxxxx</code>目录下面就是前端网络找不到的文件,是不是很抽象。<span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_annoyed.png"/></span><span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_annoyed.png"/></span></p><p>当你把文件<code>/tmpxxxx</code>目录下面文件移动到<code>1.0.187-nightly.1</code>下面,重启容器,你会发现前端不再黑屏,但是一直在加载,打开F12故技重施发现又要<code>/tmpxxxx</code>下面的文件,而且要的还是<code>1.0.187-nightly.1</code>依赖未解压的版本,实在是抽象。<span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_vomit.png"/></span><span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_vomit.png"/></span><span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_vomit.png"/></span></p><p>好了我吐槽完了,下面厘清操作。</p><h3 id="调整依赖"><a href="#调整依赖" class="headerlink" title="调整依赖"></a>调整依赖</h3><blockquote><p>在前端容器挂载目录完成。</p></blockquote><ol><li>将 <code>/1.0.187-nightly.1/tmpxxxx</code> 下面的文件移动到 <code>/1.0.187-nightly.1</code></li></ol><figure class="highlight bash"><figcaption><span>移动文件</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 去目标目录</span><br><span class="hljs-built_in">cd</span> /前端容器挂载目录/1.0.187-nightly.1<br><span class="hljs-comment"># 查询tmpxxx名字</span><br><span class="hljs-built_in">ls</span><br><span class="hljs-built_in">mv</span> ./tmpxxxx/* ./<br></code></pre></td></tr></table></figure><ol start="2"><li>下载源码,解压到 <code>./tmpxxxx</code></li></ol><figure class="highlight bash"><figcaption><span>下载解压</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 去目标目录</span><br><span class="hljs-built_in">cd</span> ./tmpxxx<br><span class="hljs-comment"># 下载解压</span><br>wget https://registry.npmjs.org/tabby-web-demo/-/tabby-web-demo-1.0.187-nightly.1.tgz tar -xvf tabby-web-demo-1.0.187-nightly.1.tgz<br></code></pre></td></tr></table></figure><h3 id="同步配置"><a href="#同步配置" class="headerlink" title="同步配置"></a>同步配置</h3><p>自此重启容器,前端能加载完毕。</p><p>访问:<code>你的域名/login</code>。可以使用Github登录。<br>登陆之后点击设置有token,然后在客户端配置即可,参考下面图片,前者前端,后者客户端。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/cae19f41855f761dd0042734dd91634f.png" alt="前端设置" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">前端设置</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/20240513115712.png" alt="客户端配置" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">客户端配置</span></div></div><h2 id="网关"><a href="#网关" class="headerlink" title="网关"></a>网关</h2><blockquote><p>编排能通过网关就没问题,网关一直重启就是证书目录有问题。</p></blockquote><p>注意:网关是WS协议,不要习惯去http反向代理,然后配置了证书的话网关地址应该是 <code>wss://网关域名:网关端口</code></p><p>填写是在前端填写,这样就能在网页上用同步的配置连接终端,如图。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/94c4cf524760ab9dc43cee6c4365dcbe.png" alt="网页使用配置" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">网页使用配置</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/eddf71237d15b46455b8ba416e9ba197.png" alt="网页通过网关连接终端" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">网页通过网关连接终端</span></div></div><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>遇事不决,欢迎提问。 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_bye.png"/></span></p>]]></content>
<summary type="html">docker-compose的tabby-web与tabby-connection-gateway的部署配置</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="部署" scheme="https://blog.thatcoder.cn/tags/%E9%83%A8%E7%BD%B2/"/>
<category term="捣腾" scheme="https://blog.thatcoder.cn/tags/%E6%8D%A3%E8%85%BE/"/>
</entry>
<entry>
<title>vue运行时反向代理无限刷新</title>
<link href="https://blog.thatcoder.cn/bug/vue-reverse-proxy-unlimited-refresh/"/>
<id>https://blog.thatcoder.cn/bug/vue-reverse-proxy-unlimited-refresh/</id>
<published>2024-05-12T14:00:00.000Z</published>
<updated>2024-05-15T01:03:29.545Z</updated>
<content type="html"><![CDATA[<h2 id="吐槽"><a href="#吐槽" class="headerlink" title="吐槽"></a>吐槽</h2><p>本以为是一次习以为常的部署业务。结果要是再刷新下去,CPU都要爆了!!!</p><h2 id="错误情景"><a href="#错误情景" class="headerlink" title="错误情景"></a>错误情景</h2><ul><li><strong>开发版本</strong>:<ul><li>vue:2</li></ul></li><li><strong>开发环境</strong>:正常本地window开发。<ul><li><strong>bug</strong>:无。</li></ul></li><li><strong>生成环境</strong>:项目是运行时,运行在52002端口,nginx反向代理127.0.0.1:52002,挂载到域名开启ssl。<ul><li><strong>bug</strong>:访问域名会无限刷新网页。刷新时候可以看到网页有内容,抓住机会点击跳转也是正常路由跳转,跳转后依然开始无限刷新。</li></ul></li></ul><p>下面是部署项目部分配置代码</p><figure class="highlight js"><figcaption><span>vue.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>)<br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br> <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'/'</span>,<br> <span class="hljs-attr">assetsDir</span>: <span class="hljs-string">'static'</span>,<br> <span class="hljs-attr">productionSourceMap</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">devServer</span>: {<br> <span class="hljs-attr">host</span>: <span class="hljs-string">'0.0.0.0'</span>,<br> <span class="hljs-attr">port</span>: <span class="hljs-number">52002</span>,<br> <span class="hljs-attr">proxy</span>: {<br> <span class="hljs-string">'/proxy/api'</span>:{<br> <span class="hljs-attr">target</span>:<span class="hljs-string">'https://xxx.thatcoder.cn'</span>,<br> <span class="hljs-attr">changeOrigin</span>:<span class="hljs-literal">true</span>,<br> <span class="hljs-attr">pathRewrite</span>:{<br> <span class="hljs-string">'/proxy/api'</span>:<span class="hljs-string">'https://xxx.thatcoder.cn'</span><br> }<br> }<br> },<br> <span class="hljs-attr">allowedHosts</span>: [<span class="hljs-string">'localhost'</span>, <span class="hljs-string">'127.0.0.1'</span>, <span class="hljs-string">'0.0.0.0'</span>, <span class="hljs-string">'xxx.thatcoder.cn'</span>],<br> }<br>};<br></code></pre></td></tr></table></figure><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><blockquote><p>严谨的说,仅提供博主排错误时看到的类似错误与解决方案收录。因为即使错误表现一致,错误原因可能不一致。</p></blockquote><h3 id="破局一"><a href="#破局一" class="headerlink" title="破局一"></a>破局一</h3><blockquote><p>此方法解决博主问题。</p></blockquote><p>执行打包命令,将反向代理的站点设置为打包的目录(默认是dist)。或者将打包文件复制到站点的目录下。(博主选择后者)</p><h3 id="破局二"><a href="#破局二" class="headerlink" title="破局二"></a>破局二</h3><p>部分人是碰到运行时的热部署经过反向代理后无法通讯导致,热部署是ws协议,与项目共用端口。部分人通过配置关闭热部署能解决。</p><ul><li>vue2和vue3关闭热部署</li></ul><figure class="highlight js"><figcaption><span>vue.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br> <span class="hljs-attr">devServer</span>: {<br> <span class="hljs-attr">hot</span>: <span class="hljs-literal">false</span><br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>vite关闭热部署</li></ul><figure class="highlight js"><figcaption><span>vite.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br> <span class="hljs-attr">server</span>: {<br> <span class="hljs-attr">hmr</span>: <span class="hljs-literal">false</span><br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="破局三"><a href="#破局三" class="headerlink" title="破局三"></a>破局三</h3><p>部分人是碰到运行时的热部署ws协议端口占用,与项目共用端口。部分人通过修改端口能解决。</p><ul><li>修改热部署ws协议端口</li></ul><figure class="highlight js"><figcaption><span>vue.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br> <span class="hljs-attr">devServer</span>: {<br> <span class="hljs-attr">client</span>: { <br> <span class="hljs-attr">webSocketURL</span>: <span class="hljs-string">'ws://0.0.0.0:52003/ws'</span> <br>},<br> }<br>}<br></code></pre></td></tr></table></figure>]]></content>
<summary type="html">服务器vue部署反向代理热重载导致无限刷新。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="BUG" scheme="https://blog.thatcoder.cn/tags/BUG/"/>
</entry>
<entry>
<title>OnlyOffice集成到Vue与Spring项目</title>
<link href="https://blog.thatcoder.cn/OnlyOffice-Vue-Spring/"/>
<id>https://blog.thatcoder.cn/OnlyOffice-Vue-Spring/</id>
<published>2024-05-11T22:00:00.000Z</published>
<updated>2024-05-12T18:28:36.050Z</updated>
<content type="html"><![CDATA[<h2 id="基础环境"><a href="#基础环境" class="headerlink" title="基础环境"></a>基础环境</h2><ul><li>需安装依赖:<code>@onlyoffice/document-editor-vue</code></li><li>需自行搭建OnlyOffice服务:<code>https://office.thatcoder.cn/</code> (仅供测试使用,请勿用于生产环境,随时可能关闭)</li><li>补充:搭建OnlyOffice要开启ssl按官方的比较麻烦,可以移步底下目录有<a href="#%E7%A4%BE%E5%8C%BA%E8%A7%A3%E9%94%81%E7%89%88">解锁版</a>,调教了配置。</li></ul><h2 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h2><blockquote><p>一口吃不成胖子,先从最简单的DEMO,来测试所用服务的可靠性。</p></blockquote><p>测试的DEMO,仅需要替换以下变量即可跑通测试。</p><ul><li><code>documentSite</code>:自行搭建的OnlyOffice服务地址。可用 <code>https://office.thatcoder.cn/</code> 进行测试。</li><li><code>documentUrl</code>: DOCX文档地址。</li></ul><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/4e6bc0de79be547da8ad0d62bd6023b0.png" data-fancybox="true"/></div></div><figure class="highlight plaintext"><figcaption><span>office-demo.vue</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><code class="hljs vue"><template><br><DocumentEditor<br> id="docEditor"<br> :documentServerUrl="documentSite"<br> :config="editorConfig"<br> :events_onDocumentReady="onDocumentReady"<br>/><br></template><br><br><script setup> <br>import {ref} from "vue"; <br>import { DocumentEditor } from '@onlyoffice/document-editor-vue'; <br>import axios from "axios"; <br><br>// DOCX文档地址<br>const documentUrl = ref('https://resumes.thatcdn.cn/public/template/resumes_001.docx'); <br>const documentTitle = ref('论Vue如何使用OnlyOffice');<br>const documentType = ref('docx'); <br>// 文档标识符<br>const documentKey = ref('your-document-key');<br>// 私有化的OnlyOffice地址<br>const documentSite = 'https://office.thatcoder.cn/'<br>// Vue处理OnlyOffice事务的回调地址<br>const documentCallSite = 'https://www.baidu.com/'<br><br>// 文档加载完毕的回调<br>const onDocumentReady = () => { <br> console.log('Document is ready'); <br>}; <br>// 文档变更的回调<br>const onDocumentStateChange = (event) => { <br> if (event.data === 'onDocumentStateChange') { <br> saveDocument(); <br> } <br>}; <br><br>// 处理文档另存为的回调事件<br>const onRequestSaveAs = (event) => { <br> console.log('Document save as requested'); <br> // 执行文档另存为的逻辑,例如弹出保存对话框等 <br>}; <br><br>// 插入图片的回调事件<br>const onRequestInsertImage = (event) => { <br> console.log('Insert image requested'); <br> // 执行插入图片的逻辑,例如打开图片选择器等 <br>}; <br><br>// 文档保存回调方法<br>const saveDocument = () => { <br> axios.post('https://www.baidu.com/save-document', { <br> documentUrl: documentUrl.value, <br> documentType: documentType.value, <br> documentKey: documentKey.value, <br> }) <br> .then(response => { <br> console.log('Document saved successfully'); <br> }) <br> .catch(error => { <br> console.error('Document save error:', error); <br> }); <br>};<br><br>// 初始化OnlyOffice<br>const editorConfig = ref({ <br> document: { <br> title: documentTitle.value, <br> url: documentUrl.value, <br> fileType: documentType.value, <br> key: documentKey.value, <br> }, <br> editorConfig: { <br> mode: 'edit', <br> lang: 'zh', <br> callbackUrl: documentCallSite, <br> }, <br> events: { <br> onReady: onDocumentReady, <br> onDocumentStateChange: onDocumentStateChange, <br> onRequestSaveAs: onRequestSaveAs, <br> onRequestInsertImage: onRequestInsertImage, <br> // 添加其他需要处理的回调事件 <br> }, <br>}); <br></script><br></code></pre></td></tr></table></figure><h2 id="配置参数"><a href="#配置参数" class="headerlink" title="配置参数"></a>配置参数</h2><blockquote><p>Config提供了大量配置,不过社区版支持的参数不是特别多。</p></blockquote><ul><li><code>editorConfig</code>:参数配置</li><li><code>document</code>:当前文档参数</li><li><code>events</code>:回调事件绑定<br>更多参考官方文档<a href="https://api.onlyoffice.com/zh/editors/config/">ONLYOFFICE API 文档 - Config</a></li></ul><figure class="highlight js"><figcaption><span>office配置</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> editorConfig = <span class="hljs-title function_">ref</span>({ <br> <span class="hljs-attr">editorConfig</span>: { <br> <span class="hljs-attr">mode</span>: <span class="hljs-string">'edit'</span>, <br> <span class="hljs-attr">lang</span>: <span class="hljs-string">'zh'</span>, <br> <span class="hljs-attr">customization</span>: { <br> <span class="hljs-attr">anonymous</span>: { <br> <span class="hljs-attr">request</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">label</span>: <span class="hljs-string">"Guest"</span> <br> }, <br> <span class="hljs-attr">autosave</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">comments</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">compactHeader</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">compactToolbar</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">compatibleFeatures</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">customer</span>: { <br> <span class="hljs-attr">address</span>: <span class="hljs-string">"A small corner of China"</span>, <br> <span class="hljs-attr">info</span>: <span class="hljs-string">"A member of the open source spirit of the Internet"</span>, <br> <span class="hljs-attr">logo</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/favicon.ico"</span>, <br> <span class="hljs-attr">logoDark</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/favicon.ico"</span>, <br> <span class="hljs-attr">mail</span>: <span class="hljs-string">"[email protected]"</span>, <br> <span class="hljs-attr">name</span>: <span class="hljs-string">"钟意"</span>, <br> <span class="hljs-attr">phone</span>: <span class="hljs-number">13305374721</span>, <br> <span class="hljs-attr">www</span>: <span class="hljs-string">"https://blog.thatcoder.cn"</span> <br> }, <br> <span class="hljs-attr">forcesave</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">goback</span>: { <br> <span class="hljs-attr">blank</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">requestClose</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">text</span>: <span class="hljs-string">"回到简历列表"</span>, <br> <span class="hljs-attr">url</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/mine/folder"</span> <br> }, <br> <span class="hljs-attr">help</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">hideRightMenu</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">hideRulers</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">integrationMode</span>: <span class="hljs-string">"embed"</span>, <br> <span class="hljs-attr">logo</span>: { <br> <span class="hljs-attr">image</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/favicon.ico"</span>, <br> <span class="hljs-attr">imageDark</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/favicon.ico"</span>, <br> <span class="hljs-attr">imageEmbedded</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn/favicon.ico"</span>, <br> <span class="hljs-attr">url</span>: <span class="hljs-string">"https://resume.app.thatcoder.cn"</span> <br> }, <br> <span class="hljs-attr">macros</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">macrosMode</span>: <span class="hljs-string">"禁用"</span>, <br> <span class="hljs-attr">mentionShare</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">mobileForceView</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">plugins</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">toolbarHideFileName</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-attr">toolbarNoTabs</span>: <span class="hljs-literal">true</span>, <br> <span class="hljs-comment">// uiTheme: "theme-dark", </span><br> <span class="hljs-attr">unit</span>: <span class="hljs-string">"厘米"</span>, <br> <span class="hljs-attr">zoom</span>: <span class="hljs-number">100</span> <br> }, <br> <span class="hljs-comment">// callbackUrl: 'https://resume.app.thatcoder.cn/onlyoffice-callback', </span><br> <span class="hljs-attr">callbackUrl</span>: <span class="hljs-string">'https://resume.thatapi.cn/office/callback'</span>, <br> }, <br> <span class="hljs-attr">document</span>: { <br> <span class="hljs-attr">title</span>: documentName, <br> <span class="hljs-attr">url</span>: documentUrl, <br> <span class="hljs-attr">fileType</span>: documentType, <br> <span class="hljs-attr">key</span>: documentKey, <br> <span class="hljs-attr">info</span>: { <br> <span class="hljs-attr">favorite</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">folder</span>: <span class="hljs-string">""</span> + thatLocal.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">'userId'</span>), <br> <span class="hljs-attr">owner</span>: <span class="hljs-string">""</span> + thatLocal.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">'userName'</span>), <br> <span class="hljs-attr">sharingSettings</span>: [ <br> { <br> <span class="hljs-attr">permissions</span>: <span class="hljs-string">"Full Access"</span>, <br> <span class="hljs-attr">user</span>: <span class="hljs-string">""</span> + thatLocal.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">'userName'</span>) <br> } <br> ], <br> <span class="hljs-attr">uploaded</span>: <span class="hljs-string">"2010-07-07 3:46 PM"</span> <br> } <br> }, <br> <span class="hljs-attr">events</span>: { <br> <span class="hljs-attr">onReady</span>: onDocumentReady, <br> <span class="hljs-comment">// onDocumentStateChange: onDocumentStateChange, </span><br> <span class="hljs-attr">onRequestSaveAs</span>: onRequestSaveAs, <br> <span class="hljs-comment">// onRequestInsertImage: onRequestInsertImage, </span><br> <span class="hljs-comment">// 添加其他需要处理的回调事件 </span><br> }, <br> <span class="hljs-comment">// documentServerUrl: 'https://office.thatcoder.cn', // 指定 ONLYOFFICE 服务器的网址 </span><br>});<br></code></pre></td></tr></table></figure><h2 id="回调服务"><a href="#回调服务" class="headerlink" title="回调服务"></a>回调服务</h2><blockquote><p>回调服务是指OnlyOffice与你的服务端的通讯地址,主要用于处理文档的保存、另存为等事件。<br>这里实例用spring演示,仅供参考,逻辑部分请自行编写。<br>建议边参考官方文档看代码。<a href="https://api.onlyoffice.com/zh/editors/callback">ONLYOFFICE API 文档 - 回调处理程序</a></p></blockquote><figure class="highlight java"><figcaption><span>OfficeCallbackDemoController.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.onestack.project.office.controller;<br><br><span class="hljs-keyword">import</span> cn.hutool.json.JSONObject;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-keyword">import</span> javax.servlet.ServletException;<br><span class="hljs-keyword">import</span> javax.servlet.http.HttpServletRequest;<br><span class="hljs-keyword">import</span> javax.servlet.http.HttpServletResponse;<br><span class="hljs-keyword">import</span> java.io.IOException;<br><span class="hljs-keyword">import</span> java.io.PrintWriter;<br><span class="hljs-keyword">import</span> java.util.Scanner;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping("/office")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OfficeCallbackDemoController</span> {<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">SAVE_DIRECTORY</span> <span class="hljs-operator">=</span> <span class="hljs-string">"/path/to/save/directory"</span>; <span class="hljs-comment">// 替换为你的保存路径</span><br><br> <span class="hljs-meta">@RequestMapping("/callback")</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">callback</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="hljs-keyword">throws</span> ServletException, IOException {<br> <span class="hljs-type">PrintWriter</span> <span class="hljs-variable">writer</span> <span class="hljs-operator">=</span> response.getWriter();<br><br> <span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(request.getInputStream()).useDelimiter(<span class="hljs-string">"\\A"</span>);<br> <span class="hljs-type">String</span> <span class="hljs-variable">body</span> <span class="hljs-operator">=</span> scanner.hasNext() ? scanner.next() : <span class="hljs-string">""</span>;<br><br> <span class="hljs-type">JSONObject</span> <span class="hljs-variable">jsonObj</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JSONObject</span>(body);<br> System.out.println(jsonObj);<br><br> <span class="hljs-keyword">if</span>(jsonObj.getInt(<span class="hljs-string">"status"</span>) == <span class="hljs-number">2</span>)<br> {<br> System.out.println(<span class="hljs-string">"Office文档可保存"</span>);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(jsonObj.getInt(<span class="hljs-string">"status"</span>) == <span class="hljs-number">1</span>)<br> {<br> System.out.println(<span class="hljs-string">"Office文档已连接"</span>);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(jsonObj.getInt(<span class="hljs-string">"status"</span>) == <span class="hljs-number">4</span>){<br> System.out.println(<span class="hljs-string">"Office文档已断开"</span>);<br> }<br> System.out.println(jsonObj);<br> writer.write(<span class="hljs-string">"{\"error\":0}"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="社区解锁版"><a href="#社区解锁版" class="headerlink" title="社区解锁版"></a>社区解锁版</h2><h3 id="镜像地址"><a href="#镜像地址" class="headerlink" title="镜像地址"></a>镜像地址</h3><p>出自oo中文交流群,企鹅群号 186184848。</p><ul><li>国内地址1:<code>docker pull registry.cn-hangzhou.aliyuncs.com/186184848/documentserver</code></li><li>国内地址2:<code>docker pull registry.cn-hangzhou.aliyuncs.com/thatcoder-public/only-office</code></li><li>DockerHub:<code>docker pull 186184848/documentserver</code></li></ul><h3 id="修改说明"><a href="#修改说明" class="headerlink" title="修改说明"></a>修改说明</h3><ul><li>基于docker镜像制作,最新版本号: 8.0.1</li><li>关闭地址过滤,导入镜像后可以完美通过IP局域网运行(在7.4以上版本默认无法通过IP访问)</li><li>去除最大在线编辑人数限制</li><li>完美解锁手机端浏览和编辑(无需设置手机UA为电脑模式)</li><li>添加中文字体,加入了常用的十几种字体</li><li>支持http/https快速部署(默认开启ssl非常繁琐)</li><li>优化文档打开速度(进一步优化,打开速度提升20%)</li><li>最大支持300M文档在线编辑(7.5以上新增)(默认30M)。</li><li>关闭SSL证书校验,自签证书也可以直接使用了。(7.6新增)</li></ul><h3 id="使用说明"><a href="#使用说明" class="headerlink" title="使用说明"></a>使用说明</h3><p>镜像包含amd64,arm64架构<br>运行镜像遇到启动不起来问题报端口错误需要加上命令:–privileged=true<br>相关教程文档请参考:<a href="https://www.yuque.com/xiaohuochai-t5dj3/wc519f/fhw8kgrwf3l8ryy2?singleDoc#">《从零使用onlyoffice及各类网盘》</a></p><h2 id="插件扩展"><a href="#插件扩展" class="headerlink" title="插件扩展"></a>插件扩展</h2><p>未来写吧,有需要的可以参考官方文档。</p>]]></content>
<summary type="html">OnlyOffice的使用记录,在Vue项目中编写文档。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="业务" scheme="https://blog.thatcoder.cn/tags/%E4%B8%9A%E5%8A%A1/"/>
</entry>
<entry>
<title>PlanetScale的免费Hobby计划即将结束</title>
<link href="https://blog.thatcoder.cn/PlanetScale%20free%20Hobby%20plan%20is%20being%20retired/"/>
<id>https://blog.thatcoder.cn/PlanetScale%20free%20Hobby%20plan%20is%20being%20retired/</id>
<published>2024-03-21T19:00:00.000Z</published>
<updated>2024-03-21T19:30:06.844Z</updated>
<content type="html"><![CDATA[<h2 id="如题"><a href="#如题" class="headerlink" title="如题"></a>如题</h2><p>三封邮件,十分钟一封,赶紧停下代码迁移数据库。</p><p>之前临时测试 waline 评论的数据,就部署在 PlanetScale,没想到一用就是一年。</p><p>白嫖这么久怪不好意思的。 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://upyun.thatcdn.cn/public/web/emojis/bilibili/bb_doge.png"/></span></p><p>不会有博友也是PlanetScale吧。</p><p><img src="https://upyun.thatcdn.cn/myself/typora/202403220321769.png" alt="PlanetScale的免费Hobby计划即将结通知"></p>]]></content>
<summary type="html">白嫖党们,PlanetScale的免费Hobby计划即将结业,届时发现,白嫖之路已经走到了尽头。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="随笔" scheme="https://blog.thatcoder.cn/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>Stellar 1.18.5 迁移到 latest</title>
<link href="https://blog.thatcoder.cn/stellar-1.18.5-to-latest/"/>
<id>https://blog.thatcoder.cn/stellar-1.18.5-to-latest/</id>
<published>2024-01-07T16:00:00.000Z</published>
<updated>2024-07-20T12:59:50.279Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>不得不说,从1.18迁移到1.23变化挺大。作者xaoxuu辛苦了。</p><h2 id="迁移工作"><a href="#迁移工作" class="headerlink" title="迁移工作"></a>迁移工作</h2><p>考虑长期使用stellar,就fork了一个分支持续跟进作者的更新。<br><img src="https://upyun.thatcdn.cn/myself/typora/202401080550812.png"></p><h2 id="变化"><a href="#变化" class="headerlink" title="变化"></a>变化</h2><p>很多细节变化吧,这里备注一下巨变。</p><h3 id="references写法改变"><a href="#references写法改变" class="headerlink" title="references写法改变"></a>references写法改变</h3><p>我wiki大量使用了参考文献功能,给出正则表达式批量替换方法</p><ul><li>查找:<code>- title: '(.*?)'\n url: '(.*?)'</code></li><li>替换:<code>- '[$1]($2)'</code></li></ul><p>也不是万能的,如果标题有特殊字符违背markdown写法可能报错,但剩下几个特殊的手动改就行。</p><h3 id="页尾license"><a href="#页尾license" class="headerlink" title="页尾license"></a>页尾license</h3><figure class="highlight yaml"><figcaption><span>_stellar.config.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">article:</span><br> <span class="hljs-attr">license:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">本文为</span> [<span class="hljs-string">$</span>{<span class="hljs-string">author.name</span>}]<span class="hljs-string">(${author.url})</span> <span class="hljs-string">撰写,采用</span> [<span class="hljs-string">CC</span> <span class="hljs-string">BY-NC-SA</span> <span class="hljs-number">4.0</span>]<span class="hljs-string">(https://creativecommons.org/licenses/by-nc-sa/4.0/)</span> <span class="hljs-string">许可协议,转载请注明出处。</span><br></code></pre></td></tr></table></figure><h3 id="作者"><a href="#作者" class="headerlink" title="作者"></a>作者</h3><figure class="highlight yaml"><figcaption><span>_data/authors.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># 作者 1 (默认)</span><br><span class="hljs-attr">ThatCoder:</span><br><span class="hljs-attr">name:</span> <span class="hljs-string">'钟意'</span><br><span class="hljs-attr">avatar:</span> <span class="hljs-string">/custom/img/author.jpg</span><br><span class="hljs-attr">banner:</span> <span class="hljs-string">https://upyun.thatcdn.cn/myself/typora/202401120247887.webp</span><br><span class="hljs-attr">description:</span> <span class="hljs-string">感谢你的阅读,</span> <span class="hljs-string">让我们拥有一段对彼此都有意义的时光.</span><br></code></pre></td></tr></table></figure><h3 id="friends标签"><a href="#friends标签" class="headerlink" title="friends标签"></a>friends标签</h3><p>friends标签的分组需要单独一个yml文件<br>sites也一样</p><h3 id="wiki系统"><a href="#wiki系统" class="headerlink" title="wiki系统"></a>wiki系统</h3><p>这个一开始有点绕,我整理了一下逻辑。</p><ol><li>_data/wiki.yml 的列表名字如 pro_name 指向 _data/wiki/pro_name.yml 的文件名字</li><li>_data/wiki/xxx.yml 文件里面的 path: /wiki/pro_path/ 参数指向 source/wiki/pro_path/ 文件夹</li><li>source/wiki/pro_path/ 文件夹内文件的 wiki: pro_name 闭环指向 _data/wiki.yml 的列表名字</li></ol><ul><li>综上 _data/wiki.yml 和 _data/wiki/pro_name.yml 和 文件wiki: pro_name 需要一致是 pro_name</li><li>而最终上线的项目在线 url 与 pro_name 无关,关联的是 source/wiki/pro_path/ 对应的 pro_path 目录名称</li></ul><h3 id="其他功能"><a href="#其他功能" class="headerlink" title="其他功能"></a>其他功能</h3><p>来不及一个一个试功能,先写到这,便把博客更新到1.23.0</p><h2 id="万能时间线重构"><a href="#万能时间线重构" class="headerlink" title="万能时间线重构"></a>万能时间线重构</h2><blockquote><p>文档待写, 测试中</p></blockquote><h3 id="联合测试"><a href="#联合测试" class="headerlink" title="联合测试"></a>联合测试</h3><div class="tag-plugin timeline timetmpl"><div class="timenode" index="0"><div class="header"><p>netease</p></div><div class="body fs14">`{ "api": "https://netease.thatapi.cn/user/event?uid=134968139&limit=10" }`</div></div><div class="timenode" index="1"><div class="header"><p>memos</p></div><div class="body fs14">`{ "api": "https://memos.thatcoder.cn/api/v1/memos?filter=creator%3D%3D%27users%2F1%27&pageSize=10" }`</div></div></div>]]></content>
<summary type="html">迁移工作还好,就是魔改不太好搬运。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Stellar" scheme="https://blog.thatcoder.cn/tags/Stellar/"/>
</entry>
<entry>
<title>微信读书自动阅读</title>
<link href="https://blog.thatcoder.cn/Tencent-WxRead-Daily/"/>
<id>https://blog.thatcoder.cn/Tencent-WxRead-Daily/</id>
<published>2023-09-14T12:00:00.000Z</published>
<updated>2023-09-18T15:28:08.045Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文章实现需要服务器, 无可视化界面亦可。<br>使用的Cookie获取上一篇文章有介绍, 顺手写了这篇。</p><p>每日一问: 我为什么要实现这个功能???</p><div class="tag-plugin link dis-select"><a class="link-card plain" title="微信读书Cookie续活" href="https://blog.thatcoder.cn/Tencent-WxRead-Cookies/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">微信读书Cookie续活</span><span class="cap link footnote">https://blog.thatcoder.cn/Tencent-WxRead-Cookies/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h2 id="机制分析"><a href="#机制分析" class="headerlink" title="机制分析"></a>机制分析</h2><p>网页版状态下阅读, 每分钟左右会有一个read请求, 通过回执可以判断是否阅读成功。<br>具体参数我不想耗费时间去逆向, 但是可以通过模拟浏览阅读页面来等待read响应进行read重播,进而轻易实现自动阅读。</p><h2 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h2><p>服务器测试了24小时, 阅读时间也是相应增加24。</p><p>有趣的是, 经测试, 每次程序运行5min, 增加的时长可能是 5min、6min、8min、11min、13min 甚至是 21min。<br>但是总时长是稳定的, 也就是说会回归一天能拉满的时间24h。</p><h2 id="实现代码"><a href="#实现代码" class="headerlink" title="实现代码"></a>实现代码</h2><p>虽说是浏览器模拟事件, 到了python的表演时间, 但是我采用了JS去写, 辅佐包是 <a href="https://playwright.dev/">Playwright</a> 。<br>总体是一次有趣的尝试。</p><h3 id="准备事项"><a href="#准备事项" class="headerlink" title="准备事项"></a>准备事项</h3><p>开始吧, 安装 Playwright</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 先创建一个文件夹</span><br><span class="hljs-built_in">mkdir</span> /server/auto/wxread && <span class="hljs-built_in">cd</span> /server/auto/wxread<br><br><span class="hljs-comment"># 安装 playwright</span><br>npm install playwright<br>npx playwright install<br><span class="hljs-comment"># 下面这个可能需要点时间</span><br><span class="hljs-comment"># 因为有浏览器的下载</span><br>npx playwright install-deps<br><br><span class="hljs-comment"># 当然少不了 axios</span><br>npm install axios<br><br><span class="hljs-comment"># 好的, 一切准备就绪, 创建代码吧</span><br></code></pre></td></tr></table></figure><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight js"><figcaption><span>wxread.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> { firefox } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'playwright'</span>);<br><span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);<br><br><span class="hljs-comment">// 获取命令行参数</span><br><span class="hljs-keyword">const</span> args = process.<span class="hljs-property">argv</span>.<span class="hljs-title function_">slice</span>(<span class="hljs-number">2</span>);<br><span class="hljs-keyword">const</span> params = {};<br>args.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">arg</span>) =></span> {<br> <span class="hljs-keyword">const</span> [key, value] = arg.<span class="hljs-title function_">split</span>(<span class="hljs-string">'='</span>);<br> <span class="hljs-keyword">if</span> (key && value) {<br> params[key] = value;<br> }<br>});<br><br><span class="hljs-keyword">const</span> url1 = <span class="hljs-string">'https://weread.qq.com/web/reader/8f5329e0813ab7d1eg012feake4d32d5015e4da3b7fbb1fa'</span>;<br><span class="hljs-keyword">const</span> url2 = <span class="hljs-string">'https://weread.qq.com/web/book/read'</span>;<br><span class="hljs-keyword">let</span> capturedResponse = <span class="hljs-literal">null</span>;<br><span class="hljs-keyword">let</span> browser = <span class="hljs-literal">null</span>;<br><br><span class="hljs-keyword">const</span> scrollInterval = <span class="hljs-number">10000</span>; <span class="hljs-comment">// 上下滑动间隔时间 单位毫秒</span><br><span class="hljs-keyword">const</span> totalTime = <span class="hljs-number">400000</span>; <span class="hljs-comment">// 单次阅读时间 单位毫秒</span><br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">getXHR</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) => {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Success: 启动 Playwright 浏览器"</span>);<br> browser = <span class="hljs-keyword">await</span> firefox.<span class="hljs-title function_">launch</span>({<br> <span class="hljs-attr">headless</span>: <span class="hljs-literal">true</span>,<br> });<br> <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.<span class="hljs-title function_">newPage</span>();<br> <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">setExtraHTTPHeaders</span>({<br> <span class="hljs-attr">cookie</span>: (<span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">get</span>(<span class="hljs-string">"https://sijnzx.laf.thatcoder.cn/tencent-weread-refcookie?key="</span>+params[<span class="hljs-string">'key'</span>])).<span class="hljs-property">data</span>[<span class="hljs-string">"data"</span>][<span class="hljs-string">"cookies"</span>]<br> });<br> <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(url1, {<br> <span class="hljs-attr">waitUntil</span>: <span class="hljs-string">'networkidle'</span>,<br> });<br><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Success: 打开内容页面"</span>);<br> page.<span class="hljs-title function_">on</span>(<span class="hljs-string">'response'</span>, <span class="hljs-keyword">async</span> (response) => {<br> <span class="hljs-keyword">if</span> (response.<span class="hljs-title function_">url</span>() === url2) {<br> <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.<span class="hljs-title function_">json</span>();<br> <span class="hljs-keyword">if</span> (data[<span class="hljs-string">'succ'</span>] === <span class="hljs-number">1</span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Success: 目标URL响应成功"</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Error: 目标URL响应失败"</span>);<br> }<br> capturedResponse = data[<span class="hljs-string">'succ'</span>] === <span class="hljs-number">1</span> ? response : <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">await</span> <span class="hljs-title function_">repeatXHR</span>(<span class="hljs-number">100</span>);<br> <span class="hljs-comment">// 不要关闭浏览器</span><br> }<br> });<br><br><span class="hljs-comment">// 定期上下滑动</span><br> <span class="hljs-keyword">let</span> scrollCount = <span class="hljs-number">0</span>; <span class="hljs-comment">// 计数器</span><br> <span class="hljs-keyword">let</span> scrollDirection = <span class="hljs-number">1</span>; <span class="hljs-comment">// 1表示向下滑动,-1表示向上滑动</span><br><br> <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () => {<br> <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">(<span class="hljs-params">scrollDirection</span>) =></span> {<br> <span class="hljs-keyword">const</span> windowHeight = <span class="hljs-variable language_">window</span>.<span class="hljs-property">innerHeight</span>;<br> <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">scrollBy</span>(<span class="hljs-number">0</span>, scrollDirection * windowHeight); <span class="hljs-comment">// 向上或向下滑动一个屏幕高度</span><br> }, scrollDirection);<br><br> scrollCount++;<br><br> <span class="hljs-comment">// 如果达到了五次滑动,切换方向并重置计数器</span><br> <span class="hljs-keyword">if</span> (scrollCount === <span class="hljs-number">5</span>) {<br> scrollDirection *= -<span class="hljs-number">1</span>; <span class="hljs-comment">// 切换方向</span><br> scrollCount = <span class="hljs-number">0</span>; <span class="hljs-comment">// 重置计数器</span><br> }<br> }, scrollInterval);<br><br><br> <span class="hljs-comment">// 设置浏览器关闭定时器</span><br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-keyword">async</span> () => {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Success: 关闭浏览器"</span>);<br> <span class="hljs-keyword">await</span> browser.<span class="hljs-title function_">close</span>();<br> }, totalTime);<br>};<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">repeatXHR</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params">count</span>) => {<br> <span class="hljs-keyword">if</span> (!capturedResponse) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Failed: 没有捕获到响应,无法重放"</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-keyword">const</span> request = capturedResponse.<span class="hljs-title function_">request</span>();<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < count; i++) {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">axios</span>({<br> <span class="hljs-attr">method</span>: request.<span class="hljs-title function_">method</span>(),<br> <span class="hljs-attr">url</span>: request.<span class="hljs-title function_">url</span>(),<br> <span class="hljs-attr">headers</span>: request.<span class="hljs-title function_">headers</span>(),<br> <span class="hljs-attr">params</span>: request.<span class="hljs-property">params</span>,<br> <span class="hljs-attr">data</span>: request.<span class="hljs-title function_">postData</span>(),<br> });<br> <span class="hljs-keyword">if</span> (response.<span class="hljs-property">data</span>.<span class="hljs-property">succ</span> !== <span class="hljs-number">1</span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Failed: 重放响应 <span class="hljs-subst">${i + <span class="hljs-number">1</span>}</span>: 失败, succ!==1`</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">`Failed: 重放响应 <span class="hljs-subst">${i + <span class="hljs-number">1</span>}</span>: 失败, <span class="hljs-subst">${error.message}</span>`</span>);<br> }<br> }<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Success: 重放响应 <span class="hljs-subst">${count}</span> 次完毕`</span>)<br>};<br><br>(<span class="hljs-keyword">async</span> () => {<br> <span class="hljs-keyword">await</span> <span class="hljs-title function_">getXHR</span>();<br>})();<br></code></pre></td></tr></table></figure><h3 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h3><p>代码会启动一个无头浏览器, 所以没有可视化也不需要担心。<br>个人测试24小时, 无任何问题, 使用的内存为300MB左右, CPU占用率为0.1%左右。<br>对了, 带上key参数是我接口的鉴权, 也就是上一篇文章的参数(个人有所修改)。<br>你实现了上一篇文章的获取可以使用你的接口。保证<code>cookie</code>是有效的即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">node wxread.js key=xxxx<br><br><span class="hljs-comment"># 成功运行大概输出如下</span><br><span class="hljs-comment"># Success: 启动 Playwright 浏览器</span><br><span class="hljs-comment"># Success: 打开内容页面</span><br><span class="hljs-comment"># Success: 目标URL响应成功</span><br><span class="hljs-comment"># Success: 重放响应 100 次完毕</span><br><span class="hljs-comment"># Success: 目标URL响应成功</span><br><span class="hljs-comment"># Success: 重放响应 100 次完毕</span><br><span class="hljs-comment"># Success: 浏览器关闭 (400秒后)</span><br></code></pre></td></tr></table></figure>]]></content>
<summary type="html">实现微信读书自动阅读, 增加阅读时长, 打卡阅读天数</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Tencent" scheme="https://blog.thatcoder.cn/tags/Tencent/"/>
</entry>
<entry>
<title>微信读书Cookies续活</title>
<link href="https://blog.thatcoder.cn/Tencent-WxRead-Cookies/"/>
<id>https://blog.thatcoder.cn/Tencent-WxRead-Cookies/</id>
<published>2023-09-13T11:00:00.000Z</published>
<updated>2023-09-18T15:28:07.961Z</updated>
<content type="html"><![CDATA[<h2 id="机制分析"><a href="#机制分析" class="headerlink" title="机制分析"></a>机制分析</h2><p>很多优秀的文章分析了延期机制, 这里列举两个</p><div class="tag-plugin link dis-select"><a class="link-card plain" title="Hank's Blog 微信读书延期机制分析" href="https://zhaohongxuan.github.io/2022/05/16/how-to-relong-cookies-in-weread/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">Hank's Blog 微信读书延期机制分析</span><span class="cap link footnote">https://zhaohongxuan.github.io/2022/05/16/how-to-relong-cookies-in-weread/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><div class="tag-plugin link dis-select"><a class="link-card plain" title="陈虚渊 微信读书数据内容接口逆向" href="https://blog.csdn.net/paycho/article/details/132796745" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">陈虚渊 微信读书数据内容接口逆向</span><span class="cap link footnote">https://blog.csdn.net/paycho/article/details/132796745</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h2 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h2><p>目前跑了几天, Cookies都能自动刷新保活</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202309132220322.png" alt="每小时自动刷新回执" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">每小时自动刷新回执</span></div></div><h2 id="主要代码"><a href="#主要代码" class="headerlink" title="主要代码"></a>主要代码</h2><p>续活我没使用代理服务器, 直接请求了</p><figure class="highlight js"><figcaption><span>refCookie</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 刷新 Cookie 的函数,模拟发送请求获取新 Cookie</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">refCookie</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params">uid: string</span>) => {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">head</span>(<span class="hljs-string">'https://weread.qq.com'</span>, { <span class="hljs-attr">headers</span>: <span class="hljs-title function_">globalHeaders</span>() });<br> <span class="hljs-keyword">if</span> (response.<span class="hljs-property">status</span> === <span class="hljs-number">200</span> || response.<span class="hljs-property">headers</span>[<span class="hljs-string">'set-cookie'</span>]) {<br> globalCookies = <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">WebArrayToString</span>(response.<span class="hljs-property">headers</span>[<span class="hljs-string">'set-cookie'</span>], globalCookies);<br> <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> <span class="hljs-title function_">upUserCookie</span>(uid)) ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span><br> }<span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="全部代码"><a href="#全部代码" class="headerlink" title="全部代码"></a>全部代码</h2><blockquote><p>运行在自己搭建的 Laf 云函数, 不能无脑抄。 代码虽烂 但已写注释。<br>需要临时使用我的接口可以联系我</p></blockquote><details class="tag-plugin colorful folding" ><summary><span>代码结构图</span></summary><div class="body"><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202309132103742.png" alt="这样也许清晰一点" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">这样也许清晰一点</span></div></div></div></details><figure class="highlight js"><figcaption><span>Serverless Code</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;<br><span class="hljs-keyword">import</span> cloud <span class="hljs-keyword">from</span> <span class="hljs-string">"@/cloud-sdk"</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * API请求入口方法</span><br><span class="hljs-comment"> */</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">main</span> = <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> (<span class="hljs-params">ctx: FunctionContext</span>) {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> { cookies, uid, refresh } = ctx.<span class="hljs-property">method</span> === <span class="hljs-string">'GET'</span> ? ctx.<span class="hljs-property">query</span> || ctx.<span class="hljs-property">params</span> : ctx.<span class="hljs-property">body</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">verifyData</span>(uid)) { <span class="hljs-comment">// 用户获取cookie请求</span><br> <span class="hljs-keyword">if</span> ((<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">verifyUser</span>(uid))) {<br> !(<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">verifyRefresh</span>(uid)) || (<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">refCookie</span>(refresh))<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">success</span>(<span class="hljs-string">"获取Cookie成功"</span>, { <span class="hljs-attr">cookies</span>: globalCookies })<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">failed</span>(<span class="hljs-string">"搞咩! "</span> + uid + <span class="hljs-string">" 不存在!"</span>);<br> }<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">verifyData</span>(cookies)) { <span class="hljs-comment">// 新增用户请求</span><br> <span class="hljs-keyword">const</span> uid = <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">StringToJson</span>(cookies)[<span class="hljs-string">'wr_vid'</span>]<br> globalCookies = cookies<br> <span class="hljs-keyword">const</span> userInfo = (<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">getUserInfo</span>(uid))<br> <span class="hljs-keyword">if</span> (!userInfo[<span class="hljs-string">'name'</span>]) {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">failed</span>(<span class="hljs-string">"搞咩! cookies 不能用!"</span>);<br> }<br> <span class="hljs-keyword">const</span> <span class="hljs-attr">userData</span>: <span class="hljs-title class_">WxReadUser</span> = {<br> <span class="hljs-string">'userVid'</span>: uid,<br> <span class="hljs-string">'userInfo'</span>: userInfo,<br> <span class="hljs-string">'cookies'</span>: globalCookies,<br> <span class="hljs-string">'cookies_uptime'</span>: (<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()).<span class="hljs-title function_">valueOf</span>(),<br> <span class="hljs-string">'cookies_life'</span>: <span class="hljs-literal">true</span><br> }<br> <span class="hljs-keyword">const</span> add = (<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">addUser</span>(userData))<br> <span class="hljs-keyword">if</span> (add.<span class="hljs-property">answer</span>) {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">success</span>(<br> <span class="hljs-string">`存入cookies成功, 未来取用cookies请通过以下方式<span class="hljs-subst">${<span class="hljs-string">'\n'</span>}</span>[ https://sijnzx.laf.thatcoder.cn/tencent-weread-refcookie?uid=您的userVid ]`</span>,<br> { <span class="hljs-attr">userVid</span>: uid, userInfo }<br> )<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">error</span>()<br> }<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">verifyData</span>(refresh)) { <span class="hljs-comment">// 刷新请求</span><br> <span class="hljs-keyword">let</span> <span class="hljs-attr">req</span>: any<br> <span class="hljs-keyword">if</span> (!(<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">verifyRefresh</span>(refresh))) {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">success</span>(<span class="hljs-string">"Cookie不需要刷新"</span>)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">refCookie</span>(refresh)) ? msgServer.<span class="hljs-title function_">success</span>(<span class="hljs-string">"刷新Cookie成功"</span>) : msgServer.<span class="hljs-title function_">error</span>()<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">failed</span>(<span class="hljs-string">"搞咩! 传的什么狗屁参数!"</span>);<br> }<br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-keyword">return</span> msgServer.<span class="hljs-title function_">error</span>()<br> }<br>};<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取数据库访问器</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">const</span> db = cloud.<span class="hljs-title function_">database</span>().<span class="hljs-title function_">collection</span>(<span class="hljs-string">'tc_tencent_wxread'</span>);<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * cookies格式工具</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">const</span> <span class="hljs-title class_">CookieUtil</span> = {<br> <span class="hljs-title class_">StringToJson</span>: <span class="hljs-function">(<span class="hljs-params">cookiesString: string</span>) =></span> {<br> <span class="hljs-keyword">const</span> cookieGroup = cookiesString.<span class="hljs-title function_">split</span>(<span class="hljs-string">'; '</span>)<br> <span class="hljs-keyword">const</span> cookieJson = {}<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < cookieGroup.<span class="hljs-property">length</span>; i++) {<br> <span class="hljs-keyword">const</span> cookieGroupJson = cookieGroup[i].<span class="hljs-title function_">split</span>(<span class="hljs-string">'='</span>)<br> cookieJson[cookieGroupJson[<span class="hljs-number">0</span>]] = cookieGroupJson.<span class="hljs-property">length</span> === <span class="hljs-number">1</span> ? <span class="hljs-string">''</span> : cookieGroupJson[<span class="hljs-number">1</span>]<br> }<br> <span class="hljs-keyword">return</span> cookieJson<br> },<br> <span class="hljs-title class_">JsonToString</span>: <span class="hljs-function">(<span class="hljs-params">cookiesJson: object</span>) =></span> {<br> <span class="hljs-keyword">const</span> keyValuePairs = [];<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> key <span class="hljs-keyword">in</span> cookiesJson) {<br> <span class="hljs-keyword">if</span> (cookiesJson.<span class="hljs-title function_">hasOwnProperty</span>(key)) {<br> <span class="hljs-keyword">const</span> value = cookiesJson[key];<br> keyValuePairs.<span class="hljs-title function_">push</span>(<span class="hljs-string">`<span class="hljs-subst">${key}</span>=<span class="hljs-subst">${value}</span>`</span>);<br> }<br> }<br> <span class="hljs-keyword">return</span> keyValuePairs.<span class="hljs-title function_">join</span>(<span class="hljs-string">'; '</span>);<br> },<br> <span class="hljs-title class_">WebArrayToString</span>: <span class="hljs-function">(<span class="hljs-params">cookiesArray: <span class="hljs-built_in">Array</span><string>, cookiesString: string</span>) =></span> {<br> <span class="hljs-keyword">let</span> cookieJson = <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">StringToJson</span>(cookiesString)<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> cookie <span class="hljs-keyword">of</span> cookiesArray) {<br> <span class="hljs-keyword">const</span> <span class="hljs-attr">refresh</span>: <span class="hljs-title class_">Array</span><string> = cookie.<span class="hljs-title function_">split</span>(<span class="hljs-string">'; '</span>)[<span class="hljs-number">0</span>].<span class="hljs-title function_">split</span>(<span class="hljs-string">'='</span>)<br> cookieJson[refresh[<span class="hljs-number">0</span>]] = refresh[<span class="hljs-number">1</span>]<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">JsonToString</span>(cookieJson)<br> },<br> <span class="hljs-title class_">StringToArray</span>: <span class="hljs-function">(<span class="hljs-params">cookiesString: string</span>) =></span> {<br> <span class="hljs-keyword">return</span> cookiesString.<span class="hljs-title function_">split</span>(<span class="hljs-string">'; '</span>)<br> }<br>}<br><br><span class="hljs-comment">// 全局变量。不合理, 但是能减少云函数单文件代码量</span><br><span class="hljs-keyword">var</span> globalCookies = <span class="hljs-string">""</span><br><span class="hljs-keyword">var</span> <span class="hljs-title function_">globalHeaders</span> = (<span class="hljs-params"></span>) => {<br> <span class="hljs-keyword">return</span> {<br> <span class="hljs-title class_">Cookie</span>: <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">StringToArray</span>(globalCookies), <span class="hljs-comment">// 传入的 Cookie 数组</span><br> <span class="hljs-title class_">Referer</span>: <span class="hljs-string">'https://weread.qq.com/'</span>,<br> <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,<br> <span class="hljs-string">'Access-Control-Allow-Methods'</span>: <span class="hljs-string">'*'</span>,<br> }<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 微信读书API方法</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">const</span> <span class="hljs-title class_">CookiesApi</span> = {<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取用户信息</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@uid</span>: 微信Cookie['wr_vid']</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">getUserInfo</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">let</span> userInfo = { <span class="hljs-string">'userVid'</span>: <span class="hljs-string">""</span> }<br> <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">get</span>(<span class="hljs-string">'https://weread.qq.com/web/user?userVid='</span> + uid, { <span class="hljs-attr">headers</span>: <span class="hljs-title function_">globalHeaders</span>() }).<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">e</span> =></span> {<br> userInfo = e.<span class="hljs-property">data</span><br> })<br> <span class="hljs-keyword">return</span> userInfo<br> },<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 验证Cookie是否存活</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@uid</span>: 微信Cookie['wr_vid']</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">verifyAlive</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">const</span> cookie = (<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">getUserCookie</span>(uid))<br> globalCookies = cookie<br> <span class="hljs-keyword">let</span> userInfo = <span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">getUserInfo</span>(uid)<br> <span class="hljs-keyword">return</span> (<span class="hljs-title class_">String</span>)(userInfo[<span class="hljs-string">'userVid'</span>]).<span class="hljs-title function_">includes</span>(uid)<br> },<br> <span class="hljs-comment">// 判断是否需要刷新 Cookie</span><br> <span class="hljs-attr">verifyRefresh</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">const</span> time = (<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">getUserCookieTime</span>(uid))<br> <span class="hljs-keyword">if</span> ( !(<span class="hljs-keyword">await</span> <span class="hljs-title class_">CookiesApi</span>.<span class="hljs-title function_">verifyAlive</span>(uid)) || (<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()).<span class="hljs-title function_">valueOf</span>() - time >= <span class="hljs-number">3600000</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> },<br> <span class="hljs-comment">// 刷新 Cookie 的函数,模拟发送请求获取新 Cookie</span><br> <span class="hljs-attr">refCookie</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">head</span>(<span class="hljs-string">'https://weread.qq.com'</span>, { <span class="hljs-attr">headers</span>: <span class="hljs-title function_">globalHeaders</span>() });<br> <span class="hljs-comment">// if (response.status === 200) {</span><br> <span class="hljs-comment">// return true</span><br> <span class="hljs-comment">// }</span><br> <span class="hljs-keyword">const</span> newCookies = response.<span class="hljs-property">headers</span>[<span class="hljs-string">'set-cookie'</span>]<br> <span class="hljs-keyword">if</span> (!newCookies) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br> globalCookies = <span class="hljs-title class_">CookieUtil</span>.<span class="hljs-title class_">WebArrayToString</span>(newCookies, globalCookies);<br> <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">upUserCookie</span>(uid)) ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span><br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br> }<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 数据库服务层。 呵, JS哪来的服务层</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">const</span> userServer = {<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 验证数据库是否存在用户</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@uid</span>: 微信Cookie['wr_vid']</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span>: {boolean}</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">verifyUser</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">where</span>({ <span class="hljs-string">'userVid'</span>: uid }).<span class="hljs-title function_">count</span>()).<span class="hljs-property">total</span> > <span class="hljs-number">0</span><br> },<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取用户Cookie</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@uid</span>: 微信Cookie['wr_vid']</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">getUserCookie</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">const</span> get = (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">where</span>({ <span class="hljs-string">'userVid'</span>: uid }).<span class="hljs-title function_">limit</span>(<span class="hljs-number">1</span>).<span class="hljs-title function_">get</span>())<br> <span class="hljs-keyword">return</span> get.<span class="hljs-property">data</span>[<span class="hljs-number">0</span>][<span class="hljs-string">'cookies'</span>]<br> },<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取用户CookieTime</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@uid</span>: 微信Cookie['wr_vid']</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">getUserCookieTime</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">const</span> get = (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">where</span>({ <span class="hljs-string">'userVid'</span>: uid }).<span class="hljs-title function_">limit</span>(<span class="hljs-number">1</span>).<span class="hljs-title function_">get</span>())<br> <span class="hljs-keyword">return</span> get.<span class="hljs-property">data</span>[<span class="hljs-number">0</span>][<span class="hljs-string">'cookies_uptime'</span>]<br> },<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 更新用户Cookie</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">upUserCookie</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">uid</span>: string) => {<br> <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">where</span>({<span class="hljs-string">'userVid'</span>: uid}).<span class="hljs-title function_">limit</span>(<span class="hljs-number">1</span>).<span class="hljs-title function_">update</span>({ <span class="hljs-attr">cookies</span>: globalCookies, <span class="hljs-attr">cookies_uptime</span>: (<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()).<span class="hljs-title function_">valueOf</span>(), <span class="hljs-attr">cookies_life</span>: <span class="hljs-literal">true</span> })).<span class="hljs-property">ok</span><br> },<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 新增数据库用户信息</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-attr">addUser</span>: <span class="hljs-keyword">async</span> (<span class="hljs-attr">userData</span>: <span class="hljs-title class_">WxReadUser</span>) => {<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">add</span>: any<br> <span class="hljs-keyword">if</span> ((<span class="hljs-keyword">await</span> userServer.<span class="hljs-title function_">verifyUser</span>(userData.<span class="hljs-property">userVid</span>))) {<br> add = (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">where</span>({<span class="hljs-string">'userVid'</span>: userData.<span class="hljs-property">userVid</span>}).<span class="hljs-title function_">limit</span>(<span class="hljs-number">1</span>).<span class="hljs-title function_">update</span>(userData))<br> } <span class="hljs-keyword">else</span> {<br> add = (<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">add</span>(userData))<br> }<br> <span class="hljs-keyword">return</span> { <span class="hljs-attr">answer</span>: add.<span class="hljs-property">ok</span>, <span class="hljs-attr">id</span>: add.<span class="hljs-property">upsertId</span> }<br> }<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 回执服务层</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">const</span> msgServer = {<br> <span class="hljs-attr">success</span>: <span class="hljs-function">(<span class="hljs-params">msg: string, data: any = {}</span>) =></span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">event</span>: <span class="hljs-string">"操作成功"</span>, <span class="hljs-attr">message</span>: msg, data })<br> },<br> <span class="hljs-attr">failed</span>: <span class="hljs-function">(<span class="hljs-params">msg: string</span>) =></span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">statusCode</span>: <span class="hljs-number">400</span>, <span class="hljs-attr">event</span>: <span class="hljs-string">"操作失败"</span>, <span class="hljs-attr">message</span>: msg })<br> },<br> <span class="hljs-attr">error</span>: <span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">statusCode</span>: <span class="hljs-number">500</span>, <span class="hljs-attr">event</span>: <span class="hljs-string">"程序错误"</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">"请联系钟意, 必应搜索钟意博客。"</span> })<br> }<br>}<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 微信用户对象接口</span><br><span class="hljs-comment"> */</span><br>interface <span class="hljs-title class_">WxReadUser</span> {<br> <span class="hljs-string">'userVid'</span>: string,<br> <span class="hljs-string">'userInfo'</span>?: any,<br> <span class="hljs-string">'cookies'</span>: string,<br> <span class="hljs-string">'cookies_uptime'</span>?: number,<br> <span class="hljs-string">'cookies_life'</span>?: boolean<br>}<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">verifyData</span> = (<span class="hljs-params">data: any</span>) => {<br> <span class="hljs-keyword">if</span> (data === <span class="hljs-literal">null</span> || data === [] || data === {} || data === <span class="hljs-literal">undefined</span> || data === <span class="hljs-string">''</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (data.<span class="hljs-property">length</span> > <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="实际应用"><a href="#实际应用" class="headerlink" title="实际应用"></a>实际应用</h2><ul><li>获取自己的微信读书信息</li><li>下载微信读书的书籍</li><li>导出书单信息</li><li>带出读书笔记</li><li>自动阅读( 这个功能有什么用? )</li></ul><div class="tag-plugin link dis-select"><a class="link-card plain" title="自动阅读微信读书" href="https://blog.thatcoder.cn/Tencent-WxRead-Daily/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">自动阅读微信读书</span><span class="cap link footnote">https://blog.thatcoder.cn/Tencent-WxRead-Daily/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div>]]></content>
<summary type="html">实现微信读书的Cookies在生命结束后自动刷新</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Tencent" scheme="https://blog.thatcoder.cn/tags/Tencent/"/>
</entry>
<entry>
<title>Linux 挂载磁盘</title>
<link href="https://blog.thatcoder.cn/Linux-Add-Device/"/>
<id>https://blog.thatcoder.cn/Linux-Add-Device/</id>
<published>2023-09-08T11:23:00.000Z</published>
<updated>2023-09-19T17:14:55.054Z</updated>
<content type="html"><![CDATA[<h2 id="大致步骤"><a href="#大致步骤" class="headerlink" title="大致步骤"></a>大致步骤</h2><ol><li>准备挂载目录</li><li>磁盘分区</li><li>格式化分区</li><li>挂载磁盘</li></ol><h2 id="创建目录"><a href="#创建目录" class="headerlink" title="创建目录"></a>创建目录</h2><p>没啥好说的, 看你喜欢啥名字</p><figure class="highlight bash"><figcaption><span>创建目录</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> -p /extra<br></code></pre></td></tr></table></figure><h2 id="磁盘分区"><a href="#磁盘分区" class="headerlink" title="磁盘分区"></a>磁盘分区</h2><p>先查看磁盘是否需要分区</p><figure class="highlight bash"><figcaption><span>磁盘信息</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">fdisk -l<br></code></pre></td></tr></table></figure><details class="tag-plugin colorful folding" ><summary><span>查看需要分区的 `Device Boot`</span></summary><div class="body"><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202309131910829.png" alt="fdisk -l 打印信息" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">fdisk -l 打印信息</span></div></div></div></details><figure class="highlight bash"><figcaption><span>开始分区</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 根据你的 Device Boot 更改 /dev/vda</span><br><br>fdisk /dev/vda<br><br><span class="hljs-comment"># 根据提示依次进行以下输入</span><br><span class="hljs-comment"># n、p、1、回车、回车、wq</span><br></code></pre></td></tr></table></figure><p>再次打印磁盘信息会有多一个区</p><h2 id="格式化分区"><a href="#格式化分区" class="headerlink" title="格式化分区"></a>格式化分区</h2><figure class="highlight bash"><figcaption><span>格式化分区</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 这里填多出来的那个 Device Boot</span><br><br>mkfs.ext4 /dev/vda1<br></code></pre></td></tr></table></figure><h2 id="挂载"><a href="#挂载" class="headerlink" title="挂载"></a>挂载</h2><p>这样修改/etc/fstab下次重启就不会丢失挂载信息</p><figure class="highlight bash"><figcaption><span>挂载</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># /dev/vda1 和 /extra 还是根据你的来</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"/dev/vda1 /extra ext4 defaults 0 0"</span> >> /etc/fstab<br></code></pre></td></tr></table></figure><h2 id="Extra"><a href="#Extra" class="headerlink" title="Extra"></a>Extra</h2><p>顺手记录几种查看磁盘UUID方法</p><figure class="highlight bash"><figcaption><span>查看UUID</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 块设备信息 树形</span><br>lsblk -o name,mountpoint,size,uuid<br><span class="hljs-comment"># 查看/etc/fstab 文件</span><br><span class="hljs-built_in">cat</span> /etc/fstab<br><span class="hljs-comment"># 块设备信息</span><br>blkid<br><span class="hljs-built_in">ls</span> -lh /dev/disk/by-uuid/<br></code></pre></td></tr></table></figure>]]></content>
<summary type="html">备份一下Linux挂载磁盘方法, 盘要满了 淦</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Linux" scheme="https://blog.thatcoder.cn/tags/Linux/"/>
</entry>
<entry>
<title>WSL 使用 Window 网络代理</title>
<link href="https://blog.thatcoder.cn/WSL%E4%BD%BF%E7%94%A8Window%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86/"/>
<id>https://blog.thatcoder.cn/WSL%E4%BD%BF%E7%94%A8Window%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86/</id>
<published>2023-08-29T16:00:00.000Z</published>
<updated>2023-08-29T16:00:00.000Z</updated>
<content type="html"><![CDATA[<h2 id="互通端口"><a href="#互通端口" class="headerlink" title="互通端口"></a>互通端口</h2><ul><li><p>开启Window与WSL的互通</p><figure class="highlight bash"><figcaption><span>管理员模式</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">New-NetFirewallRule -DisplayName <span class="hljs-string">"WSL"</span> -Direction Inbound -InterfaceAlias <span class="hljs-string">"vEthernet (WSL (Hyper-V firewall))"</span> -Action Allow<br></code></pre></td></tr></table></figure></li><li><p>开启WSL与Clash的互通</p><figure class="highlight bash"><figcaption><span>管理员模式</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">New-NetFirewallRule -DisplayName <span class="hljs-string">"WSL-Proxy"</span> -Direction Inbound -Protocol TCP -LocalPort 7890 -Action Allow<br></code></pre></td></tr></table></figure></li></ul><h2 id="开启Clash"><a href="#开启Clash" class="headerlink" title="开启Clash"></a>开启Clash</h2><ul><li>略过安装</li><li>在Clash的第一个菜单开启允许局域网代理,让子网都能走Clash代理</li></ul><h2 id="配置WSL"><a href="#配置WSL" class="headerlink" title="配置WSL"></a>配置WSL</h2><ul><li><p>打开WSL,输入以下命令编辑配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo vim ~/.bashrc<br></code></pre></td></tr></table></figure></li><li><p>在文件末尾添加以下内容,检查一下你的WSL里面是否是172.23.16.1对应Windows的IP地址</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># clash</span><br><span class="hljs-built_in">export</span> http_proxy=<span class="hljs-string">"http://172.23.16.1:7890"</span><br><span class="hljs-built_in">export</span> https_proxy=<span class="hljs-string">"http://172.23.16.1:7890"</span><br><span class="hljs-comment"># clash end</span><br></code></pre></td></tr></table></figure></li><li><p>保存并退出,刷新</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">source</span> ~/.bashrc<br></code></pre></td></tr></table></figure></li><li><p>验证是否生效</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">curl -v -L https://www.youtube.com<br></code></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html">WSL WSL2 使用 Window 的代理科学上网</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Linux" scheme="https://blog.thatcoder.cn/tags/Linux/"/>
</entry>
<entry>
<title>SpringBoot集成支付宝支付的前后端相关逻辑</title>
<link href="https://blog.thatcoder.cn/Spring-Pay-Ali/"/>
<id>https://blog.thatcoder.cn/Spring-Pay-Ali/</id>
<published>2023-08-20T02:00:00.000Z</published>
<updated>2024-08-09T17:36:25.521Z</updated>
<content type="html"><![CDATA[<p>从支付宝的开发者配置到项目的支付接口实际落地,记录一下Spring Boot集成支付宝支付的全过程。<br>待完善。。。</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ol><li>本文提供支付宝沙盒环境</li><li>开发语言:Spring Boot + Kotlin + Vue (Java开发者肯定能看懂)</li><li>业务逻辑代码会部分省略,更多是文字引导思考,请结合自己的业务场景进行实现。</li></ol><h2 id="开发筹备"><a href="#开发筹备" class="headerlink" title="开发筹备"></a>开发筹备</h2><h3 id="需求参数"><a href="#需求参数" class="headerlink" title="需求参数"></a>需求参数</h3><blockquote><p>只负责对接业务 或 使用本文沙盒环境 的同学,请移步 <a href="#%E4%BE%9D%E8%B5%96%E9%85%8D%E7%BD%AE">依赖配置</a></p></blockquote><ol><li>应用ID</li><li>应用AES密钥</li><li>应用公钥</li><li>应用私钥</li><li>支付宝公钥</li><li>支付宝根证书</li></ol><h3 id="支付申请流程"><a href="#支付申请流程" class="headerlink" title="支付申请流程"></a>支付申请流程</h3><blockquote><p>以下流程按照需求参数的顺序。</p></blockquote><ol><li>登录<a href="https://open.alipay.com/develop/manage">支付宝开发者平台</a>,创建应用,进入应用详情页,左上角有 <code>应用ID</code>。</li><li>点击左边菜单栏的 <code>开发者设置</code>,点击 <code>接口内容加密方式</code> 会得到一个 <code>AES密钥</code>。</li><li>点击左边菜单栏的 <code>开发者设置</code>,点击 <code>接口加签方式</code> 选择证书模式然后走官方教程,按照教程会在本地生成得到 <code>应用公钥</code> 和 <code>应用私钥</code>。</li><li>第三步之后,再点击 <code>接口加签方式</code>,能下载得到 <code>支付宝根证书</code> 和 <code>支付宝公钥</code>。</li><li>至此得到全部参数,请保存好。最后还是在<code>开发者设置</code>中,将计划的 <code>授权回调地址</code> 填入(也就是后端项目接收支付宝平台回调支付结果的地址)。</li></ol><ul><li>还有一件事是去<a href="https://b.alipay.com/page/product-workspace/all-product">支付宝B端</a>申请开通对应的支付功能产品,比如电脑网站支付、手机网站支付、APP支付、JSAPI支付等。<br>没申请的话可以先申请再看下面的教程,节省等待审核的时间。</li></ul><h2 id="依赖配置"><a href="#依赖配置" class="headerlink" title="依赖配置"></a>依赖配置</h2><h3 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h3><blockquote><p><a href="https://central.sonatype.com/artifact/com.alipay.sdk/alipay-sdk-java/overview">依赖参考文档</a></p></blockquote><div class="tag-plugin tabs"id="tab_2"><div class="nav-tabs"><div class="tab active"><a href="#tab_2-1">Maven</a></div><div class="tab"><a href="#tab_2-2">Gradle(Kotlin)</a></div><div class="tab"><a href="#tab_2-3">Gradle</a></div></div><div class="tab-content"><div class="tab-pane active" id="tab_2-1"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><dependency><br> <groupId>com.alipay.sdk</groupId><br> <artifactId>alipay-sdk-java</artifactId><br> <version>4.39.134.ALL</version><br></dependency><br></code></pre></td></tr></table></figure></div><div class="tab-pane" id="tab_2-2"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">implementation("com.alipay.sdk:alipay-sdk-java:4.39.134.ALL")<br></code></pre></td></tr></table></figure></div><div class="tab-pane" id="tab_2-3"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">implementation group: 'com.alipay.sdk', name: 'alipay-sdk-java', version: '4.39.134.ALL'<br></code></pre></td></tr></table></figure></div></div></div><h3 id="配置资源"><a href="#配置资源" class="headerlink" title="配置资源"></a>配置资源</h3><blockquote><p>我这里给出我的配置解决方案,具体请结合自己业务场景储存与使用这些资源,请各显神通。</p></blockquote><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/b9aaba6bbab7e303e5fc4c5f14b0f822.png" alt="项目资源与配置" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">项目资源与配置</span></div></div><figure class="highlight yaml"><figcaption><span>application.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">pay:</span><br> <span class="hljs-attr">alipay:</span><br> <span class="hljs-attr">appId:</span> <span class="hljs-string">/appId.txt</span> <span class="hljs-comment"># 应用id</span><br> <span class="hljs-attr">privateKey:</span> <span class="hljs-string">/privateKey.txt</span> <span class="hljs-comment"># 应用私钥</span><br> <span class="hljs-attr">appCert:</span> <span class="hljs-string">/appCert.crt</span> <span class="hljs-comment"># 应用公钥</span><br> <span class="hljs-attr">alipayPublicCert:</span> <span class="hljs-string">/alipayPublicCert.crt</span> <span class="hljs-comment"># 支付宝公钥路径</span><br> <span class="hljs-attr">rootCert:</span> <span class="hljs-string">/rootCert.crt</span> <span class="hljs-comment"># 支付宝根证书路径</span><br> <span class="hljs-attr">encryptKey:</span> <span class="hljs-string">/encryptKey.txt</span> <span class="hljs-comment"># 应用AES密钥</span><br> <span class="hljs-attr">serverUrl:</span> <span class="hljs-string">/serverUrl.txt</span> <span class="hljs-comment"># 支付宝网关地址 线上环境:https://openapi.alipay.com/gateway.do</span><br> <span class="hljs-attr">notifyUrl:</span> <span class="hljs-string">xxxx</span> <span class="hljs-comment"># 授权回调地址</span><br> <span class="hljs-attr">returnUrl:</span> <span class="hljs-string">xxxx</span> <span class="hljs-comment"># 支付成功后前端地址</span><br></code></pre></td></tr></table></figure><h3 id="客户端类"><a href="#客户端类" class="headerlink" title="客户端类"></a>客户端类</h3><blockquote><p>都是老司机看使用的类包名就知道干什么的,到了开发这步就不献丑解释代码。请自行设计一个支付宝配置类,然后注入到 Spring Bean 中。<br>至于我示例代码的 getAbsolutePath 方法,是Linux部署没有resources文件夹,所以用这个方法构造一个虚拟的绝对路径。</p></blockquote><blockquote><p>通过下面代码我们得到了一个支付宝支付客户端,后文会用来发起支付请求。</p></blockquote><figure class="highlight kotlin"><figcaption><span>ALiPayConfig.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">package</span> cn.thatcoder.auto.system.pay.config<br><br><span class="hljs-keyword">import</span> cn.hutool.core.io.FileUtil<br><span class="hljs-keyword">import</span> com.alipay.api.AlipayClient<br><span class="hljs-keyword">import</span> com.alipay.api.AlipayConfig<br><span class="hljs-keyword">import</span> com.alipay.api.AlipayConstants<br><span class="hljs-keyword">import</span> com.alipay.api.DefaultAlipayClient<br><span class="hljs-keyword">import</span> org.springframework.beans.factory.<span class="hljs-keyword">annotation</span>.Value<br><span class="hljs-keyword">import</span> org.springframework.context.<span class="hljs-keyword">annotation</span>.Bean<br><span class="hljs-keyword">import</span> org.springframework.context.<span class="hljs-keyword">annotation</span>.Configuration<br><span class="hljs-keyword">import</span> org.springframework.core.io.ResourceLoader<br><span class="hljs-keyword">import</span> java.nio.file.Files<br><span class="hljs-keyword">import</span> java.nio.file.StandardCopyOption<br><br><span class="hljs-meta">@Configuration(<span class="hljs-string">"支付宝支付配置"</span>)</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ALiPayConfig</span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> resourceLoader: ResourceLoader) {<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.appId}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> appId: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.privateKey}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> privateKey: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.appCert}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> appCert: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.alipayPublicCert}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> alipayPublicCert: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.rootCert}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> rootCert: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.encryptKey}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> encryptKey: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.returnUrl}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> returnUrl: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.notifyUrl}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> notifyUrl: String<br><br> <span class="hljs-meta">@Value(<span class="hljs-string">"\${pay.alipay.serverUrl}"</span>)</span><br> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> serverUrl: String<br><br> <span class="hljs-keyword">var</span> out_no_prefix = <span class="hljs-string">"TC_A_"</span><br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 支付宝支付客户端</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">alipayClient</span><span class="hljs-params">()</span></span>: AlipayClient {<br> <span class="hljs-keyword">val</span> config = AlipayConfig().apply {<br> <span class="hljs-keyword">this</span>.serverUrl = FileUtil.readUtf8String(getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.serverUrl))<br> <span class="hljs-keyword">this</span>.appId = FileUtil.readUtf8String(getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.appId))<br> <span class="hljs-keyword">this</span>.privateKey = FileUtil.readUtf8String(getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.privateKey))<br> <span class="hljs-keyword">this</span>.appCertPath = getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.appCert)<br> <span class="hljs-keyword">this</span>.format = AlipayConstants.FORMAT_JSON<br> <span class="hljs-keyword">this</span>.charset = AlipayConstants.CHARSET_UTF8<br> <span class="hljs-keyword">this</span>.alipayPublicCertPath = getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.alipayPublicCert)<br> rootCertPath = getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.rootCert)<br> signType = AlipayConstants.SIGN_TYPE_RSA2<br> encryptKey = FileUtil.readUtf8String(getAbsolutePath(<span class="hljs-keyword">this</span><span class="hljs-symbol">@ALiPayConfig</span>.encryptKey))<br> encryptType = AlipayConstants.ENCRYPT_TYPE_AES<br> }<br> <span class="hljs-keyword">val</span> alipayClient = DefaultAlipayClient(config)<br> alipayClient.let {<br> it.setMaxIdleConnections(<span class="hljs-number">10</span>) <span class="hljs-comment">// 连接池最大可缓存空闲数</span><br> it.setKeepAliveDuration(<span class="hljs-number">10000L</span>) <span class="hljs-comment">//连接池空闲连接的存活时间</span><br> it.setConnectTimeout(<span class="hljs-number">3000</span>) <span class="hljs-comment">//连接超时时间</span><br> it.setReadTimeout(<span class="hljs-number">15000</span>) <span class="hljs-comment">//读取超时时间</span><br> }<br> <span class="hljs-keyword">return</span> alipayClient<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 构建资源文件缓存堆路径</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAbsolutePath</span><span class="hljs-params">(localPath: <span class="hljs-type">String</span>)</span></span>: String {<br> <span class="hljs-keyword">val</span> resource =<br> resourceLoader.getResource(<span class="hljs-string">"classpath:pay/alipay<span class="hljs-subst">${localPath}</span>"</span>)<br> <span class="hljs-keyword">val</span> fileName = localPath.substring(<span class="hljs-number">1</span>)<br> <span class="hljs-keyword">val</span> tempFile = Files.createTempFile(fileName.split(<span class="hljs-string">"."</span>).first(), <span class="hljs-string">"."</span> + fileName.split(<span class="hljs-string">"."</span>).last())<br> Files.copy(resource.inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING)<br> <span class="hljs-keyword">return</span> tempFile.toAbsolutePath().toString()<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="业务逻辑"><a href="#业务逻辑" class="headerlink" title="业务逻辑"></a>业务逻辑</h2><blockquote><p>先上支付宝时序图,后面就知道自己该干嘛。</p></blockquote><ul><li>后端:被前端唤起支付,向支付宝发起支付,给前端返回支付码,等待支付宝告知支付结果。</li><li>前端:被用户申请支付,请求后端拿支付码,渲染支付页面,等待(支付宝成功会跳转到returnUrl 或者 轮询后端支付结果)</li><li>用户:哥们你管我怎么操作,我点了支付扫码再取消支付你都管不着。诶,就是点着玩儿。</li></ul><div class="tag-plugin image"><div class="image-bg"><img src="https://gw.alipayobjects.com/os/skylark-tools/public/files/0ba3e82ad37ecf8649ee4219cfe9d16b.png%26originHeight%3D2023%26originWidth%3D2815%26size%3D526149%26status%3Ddone%26width%3D2815" alt="支付宝时序图" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">支付宝时序图</span></div></div><h2 id="后端开发"><a href="#后端开发" class="headerlink" title="后端开发"></a>后端开发</h2><ul><li>后端:被前端唤起支付,向支付宝发起支付,给前端返回支付码,等待支付宝告知支付结果。</li></ul><ol><li>被前端唤起:前端携带订单号发起请求,后端<code>ALiPayController</code> 接收请求,调用支付宝Service层生成支付码,返回给前端。所以需要 <code>ALiPayService</code> 。</li><li>向支付宝发起支付:<code>ALiPayService</code> 调用支付宝客户端 <code>alipayClient</code> 根据订单信息生成支付请求,得到支付码。所以需要<code>OrderService</code> 和 <code>ShopService</code> 。</li><li>等待支付宝告知支付结果:支付宝支付成功或失败后,支付宝会回调 <code>notifyUrl</code>,通知后端支付详情,记录到数据库。所以 <code>ALiPayController</code> 最起码需要唤起支付和支付回调两个方法,还需要<code>PayDetailService</code>。</li></ol><h3 id="商品服务"><a href="#商品服务" class="headerlink" title="商品服务"></a>商品服务</h3><blockquote><p>随便写几个服务,配合讲解支付宝支付流程。</p></blockquote><figure class="highlight kotlin"><figcaption><span>Shop.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-meta">@Entity</span><br><span class="hljs-meta">@Table(name = <span class="hljs-string">"shop"</span>)</span><br><span class="hljs-meta">@Component(<span class="hljs-string">"商品"</span>)</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ShopThesis</span>{<br> <span class="hljs-meta">@Id</span><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"id"</span>)</span><br> <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br> <span class="hljs-keyword">var</span> id: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"title"</span>, length = 255)</span><br> <span class="hljs-keyword">var</span> title: String = <span class="hljs-string">""</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"desc"</span>)</span><br> <span class="hljs-keyword">var</span> desc: String = <span class="hljs-string">""</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"price"</span>)</span><br> <span class="hljs-keyword">var</span> price: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br>}<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>ShopRepository.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.<span class="hljs-keyword">data</span>.jpa.repository.JpaRepository<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Repository<br><br><span class="hljs-meta">@Repository</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">ShopRepository</span> : <span class="hljs-type">JpaRepository</span><<span class="hljs-type">Shop, Long</span>> {}<br></code></pre></td></tr></table></figure><h3 id="订单服务"><a href="#订单服务" class="headerlink" title="订单服务"></a>订单服务</h3><figure class="highlight kotlin"><figcaption><span>Order.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-meta">@Entity</span><br><span class="hljs-meta">@Table(name = <span class="hljs-string">"order"</span>)</span><br><span class="hljs-meta">@Component(<span class="hljs-string">"订单"</span>)</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderThesis</span>{<br> <span class="hljs-meta">@Id</span><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"id"</span>)</span><br> <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br> <span class="hljs-keyword">var</span> id: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br> <br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"order_no"</span>)</span><br> <span class="hljs-keyword">var</span> orderNo: String = <span class="hljs-string">""</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"user_id"</span>)</span><br> <span class="hljs-keyword">var</span> userId: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br> <br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"pay_id"</span>)</span><br> <span class="hljs-keyword">var</span> payId: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"shop_id"</span>)</span><br> <span class="hljs-keyword">var</span> shopId: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"price"</span>)</span><br> <span class="hljs-keyword">var</span> price: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"status"</span>)</span><br> <span class="hljs-keyword">var</span> status: <span class="hljs-built_in">Int</span> = <span class="hljs-number">0</span><br>}<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>OrderRepository.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.<span class="hljs-keyword">data</span>.jpa.repository.JpaRepository<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Repository<br><br><span class="hljs-meta">@Repository</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">OrderRepository</span> : <span class="hljs-type">JpaRepository</span><<span class="hljs-type">Order, Long</span>> {}<br></code></pre></td></tr></table></figure><h3 id="流水服务"><a href="#流水服务" class="headerlink" title="流水服务"></a>流水服务</h3><figure class="highlight kotlin"><figcaption><span>PayDetail.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-meta">@Entity</span><br><span class="hljs-meta">@Table(name = <span class="hljs-string">"pay_detail"</span>)</span><br><span class="hljs-meta">@Component(<span class="hljs-string">"支付流水"</span>)</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">PayDetailThesis</span>{<br> <span class="hljs-meta">@Id</span><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"id"</span>)</span><br> <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br> <span class="hljs-keyword">var</span> id: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"order_id"</span>)</span><br> <span class="hljs-keyword">var</span> orderId: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"pay_id"</span>)</span><br> <span class="hljs-keyword">var</span> payId: <span class="hljs-built_in">Long</span> = <span class="hljs-number">0</span><br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"pay_time"</span>)</span><br> <span class="hljs-keyword">var</span> payTime: LocalDateTime = LocalDateTime.now()<br><br> <span class="hljs-meta">@Column(name = <span class="hljs-string">"status"</span>)</span><br> <span class="hljs-keyword">var</span> status: <span class="hljs-built_in">Int</span> = <span class="hljs-number">0</span><br>}<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>PayDetailRepository.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">import</span> org.springframework.<span class="hljs-keyword">data</span>.jpa.repository.JpaRepository<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Repository<br><br><span class="hljs-meta">@Repository</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">PayDetailRepository</span> : <span class="hljs-type">JpaRepository</span><<span class="hljs-type">PayDetail, Long</span>> {}<br></code></pre></td></tr></table></figure><h3 id="支付宝服务"><a href="#支付宝服务" class="headerlink" title="支付宝服务"></a>支付宝服务</h3><blockquote><p>前面分析到 <code>ALiPayController</code> 最起码需要唤起支付和支付回调两个方法,所以服务层最少也要有这两个方法。<br>官网参数比较多,这里只列出核心参数。方便读者快速测试。</p></blockquote><figure class="highlight kotlin"><figcaption><span>IALiPayService.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 支付宝支付服务接口</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">IALiPayService</span> {<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 发起支付,返回支付码</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">nativePay</span><span class="hljs-params">(orderId: <span class="hljs-type">Long</span>)</span></span>: String<br> <br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 支付结果回调</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">nativeNotify</span><span class="hljs-params">(request: <span class="hljs-type">HttpServletRequest</span>)</span></span>: <span class="hljs-built_in">Boolean</span><br>}<br></code></pre></td></tr></table></figure><figure class="highlight kotlin"><figcaption><span>ALiPayService.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 支付宝支付服务实现</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ALiPayService</span>(<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> alipayClient: AlipayClient,<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> aliPayConfig: ALiPayConfig<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> orderRepository: OrderRepository,<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> shopRepository: ShopRepository,<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> payDetailRepository: PayDetailRepository,<br>): IALiPayService {<br><br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">nativePay</span><span class="hljs-params">(orderId: <span class="hljs-type">Long</span>)</span></span>: String {<br> <span class="hljs-keyword">val</span> findOrder = orderRepository.findById(orderId)<br> <span class="hljs-comment">// 处理空订单</span><br> <span class="hljs-keyword">if</span> (findOrder.isEmpty) {<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"order <span class="hljs-variable">$orderId</span> not found"</span><br> }<br> <span class="hljs-keyword">val</span> order = findOrder.<span class="hljs-keyword">get</span>()<br> <span class="hljs-keyword">return</span> questALiPay(order)<br> }<br> <br> <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">nativeNotify</span><span class="hljs-params">(request: <span class="hljs-type">HttpServletRequest</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {<br> <span class="hljs-comment">// 解析支付宝回调参数</span><br> <span class="hljs-keyword">val</span> params = request.parameterMap<br> <span class="hljs-keyword">val</span> notifyParams = HashMap<String, String>()<br> params.forEach { (k, v) -><br> notifyParams[k] = v[<span class="hljs-number">0</span>]<br> }<br> <span class="hljs-comment">// 验证签名</span><br> <span class="hljs-keyword">val</span> sign = notifyParams.remove(<span class="hljs-string">"sign"</span>)<br> <span class="hljs-keyword">val</span> signType = notifyParams.remove(<span class="hljs-string">"sign_type"</span>)<br> <span class="hljs-keyword">val</span> alipayPublicKey = aliPayConfig.alipayPublicCert<br> <span class="hljs-keyword">val</span> alipayPublicKeyInputStream = FileUtil.getInputStream(alipayPublicKey)<br> <span class="hljs-keyword">val</span> alipayPublicKeyObject = CertificateFactory.getInstance(<span class="hljs-string">"X.509"</span>).generateCertificate(alipayPublicKeyInputStream)<br> <span class="hljs-keyword">val</span> publicKey = alipayPublicKeyObject.publicKey<br> <span class="hljs-keyword">val</span> verifyResult = AlipaySignature.rsaCheckV1(notifyParams, sign, publicKey, signType)<br> <span class="hljs-keyword">if</span> (!verifyResult) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br> <span class="hljs-comment">// 处理支付结果</span><br> <span class="hljs-keyword">val</span> tradeNo = notifyParams[<span class="hljs-string">"trade_no"</span>]<br> <span class="hljs-keyword">val</span> outTradeNo = notifyParams[<span class="hljs-string">"out_trade_no"</span>]<br> <span class="hljs-keyword">val</span> tradeStatus = notifyParams[<span class="hljs-string">"trade_status"</span>]<br> <span class="hljs-keyword">val</span> findOrder = orderRepository.findByOrderNo(outTradeNo.removePrefix(aliPayConfig.out_no_prefix))<br> <span class="hljs-keyword">if</span> (findOrder.isEmpty) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br> }<br> <span class="hljs-keyword">val</span> order = findOrder.<span class="hljs-keyword">get</span>()<br> <span class="hljs-keyword">val</span> payDetail = PayDetail(<br> orderId = order.id,<br> payId = tradeNo.toLong(),<br> payTime = LocalDateTime.now(),<br> status = <span class="hljs-keyword">if</span> (tradeStatus == <span class="hljs-string">"TRADE_SUCCESS"</span>) <span class="hljs-number">1</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0</span><br> )<br> payDetailRepository.save(payDetail)<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br> }<br> <br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 申请支付</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> order 订单</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 支付链接</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">questALiPay</span><span class="hljs-params">(order: <span class="hljs-type">Order</span>)</span></span>: String {<br> <span class="hljs-keyword">val</span> shop = shopRepository.findById(order.shopId)<br> <br> <span class="hljs-comment">// 构建业务参数 JSON 对象</span><br> <span class="hljs-keyword">val</span> bizContent = JSONObject().apply {<br> <span class="hljs-comment">// 设置商户订单号</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"out_trade_no"</span>, aliPayConfig.out_no_prefix + order.orderId.toString())<br> <span class="hljs-comment">// 设置支付场景</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"scene"</span>, <span class="hljs-string">"bar_code"</span>)<br> <span class="hljs-comment">// 设置订单标题</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"subject"</span>, shop.title)<br> <span class="hljs-comment">// 设置订单总金额</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"total_amount"</span>, order.price * <span class="hljs-number">0.01</span>)<br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"product_code"</span>, <span class="hljs-string">"FAST_INSTANT_TRADE_PAY"</span>)<br> <span class="hljs-comment">// 设置订单绝对超时时间</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"timeout_express"</span>, <span class="hljs-string">"120m"</span>)<br> <span class="hljs-comment">// 设置前端支付页面模式 1 前端嵌入式 2 跳转支付宝官网</span><br> <span class="hljs-keyword">set</span>(<span class="hljs-string">"qr_pay_mode"</span>, <span class="hljs-number">1</span>)<br> }<br><br> <span class="hljs-comment">// 构建请求对象</span><br> <span class="hljs-keyword">val</span> request = AlipayTradePagePayRequest().apply {<br> <span class="hljs-comment">// 设置业务参数</span><br> <span class="hljs-keyword">this</span>.bizContent = JSONUtil.toJsonStr(bizContent)<br> <span class="hljs-comment">// 设置通知回调 URL</span><br> notifyUrl = aliPayConfig.notifyUrl<br> <span class="hljs-comment">// 设置返回支付后前端页面的跳转URL</span><br> returnUrl = aliPayConfig.returnUrl<br> <span class="hljs-comment">// 设置终端类型</span><br> terminalType = <span class="hljs-string">"WEB"</span><br> <span class="hljs-comment">// 设置产品代码</span><br> prodCode = <span class="hljs-string">"FAST_INSTANT_TRADE_PAY"</span><br> <span class="hljs-comment">// 设置是否开启了AES加密</span><br> isNeedEncrypt = <span class="hljs-literal">true</span><br> }<br><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// 发送请求并获取响应</span><br> <span class="hljs-keyword">val</span> response: AlipayTradePagePayResponse = alipayClient.pageExecute(request, <span class="hljs-string">"POST"</span>);<br> <span class="hljs-keyword">if</span> (response.isSuccess) {<br> <span class="hljs-comment">// 返回支付链接</span><br> response.body.toString()<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-string">"支付宝支付请求失败,原因: <span class="hljs-subst">${response.subMsg}</span>"</span><br> }<br> } <span class="hljs-keyword">catch</span> (e: AlipayApiException) {<br> <span class="hljs-string">"支付宝支付请求失败,原因: <span class="hljs-subst">${e.message}</span>"</span><br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="前端唤起"><a href="#前端唤起" class="headerlink" title="前端唤起"></a>前端唤起</h3><blockquote><p>被前端唤起就是一个Controller接口,前端发起请求,后端调用支付宝Service层实现生成支付码,返回给前端。<br>可以先写</p></blockquote><figure class="highlight kotlin"><figcaption><span>ALiPayController.kt</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-meta">@RestController(<span class="hljs-string">"aliPayController"</span>)</span><br><span class="hljs-meta">@RequestMapping(<span class="hljs-string">"/pay"</span>)</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ALiPayController</span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> alipayService: ALiPayService) {<br> <span class="hljs-meta">@GetMapping(<span class="hljs-string">"/ali.pay"</span>)</span><br> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">aliPay</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam(<span class="hljs-string">"orderId"</span>)</span> orderId: <span class="hljs-type">Long</span>)</span></span>: ResponseEntity<ResponseBody> {<br> <span class="hljs-keyword">val</span> codeUrl = alipayService.nativePay(orderId)<br> <span class="hljs-keyword">return</span> Response.success(<span class="hljs-string">"订单申请成功"</span>, mapOf(<span class="hljs-string">"codeUrl"</span> to codeUrl))<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="引入项目"><a href="#引入项目" class="headerlink" title="引入项目"></a>引入项目</h3><p>待写。。。</p>]]></content>
<summary type="html">从支付宝的开发者配置到项目的支付接口实际落地,记录一下Spring Boot集成支付宝支付的全过程。</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="业务" scheme="https://blog.thatcoder.cn/tags/%E4%B8%9A%E5%8A%A1/"/>
</entry>
<entry>
<title>Stellar 提高时间线适配范围</title>
<link href="https://blog.thatcoder.cn/Stellar-Timeline-More/"/>
<id>https://blog.thatcoder.cn/Stellar-Timeline-More/</id>
<published>2023-08-19T02:00:00.000Z</published>
<updated>2024-07-21T09:19:51.531Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><strong>注意:迁移1.22.1后本文作废,已完成便于主题更新的方案,待写文档中</strong></p><p>某天想把其它app的动态放进时间线, 但每个app接口都返回不同的json数据格式, 即使同一个app不同提取项目也是不同的json数据格式,<br>便不了了之。<br>直到前几天萌生一个想法: 通过传入有效路径匹配提取对应的json数据。但是这样代码太长就不推送了, 也需要的人自己加进去(<br>不影响主题升级)</p><h2 id="目前成果"><a href="#目前成果" class="headerlink" title="目前成果"></a>目前成果</h2><ul><li>编写路径即可匹配数据</li><li>编写路径时赋予路径类型可生成对应类型组件</li><li>允许多个api聚合到一个时间线展示</li><li>有时间字段可按照时间排序</li><li>排除包含的内容、正则匹配替换内容</li></ul><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202308131428294.png" alt="聚合的时间线" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">聚合的时间线</span></div></div><h2 id="加入主题"><a href="#加入主题" class="headerlink" title="加入主题"></a>加入主题</h2><blockquote></blockquote><p>经常使用git的coder直接看提交吧 <a href="https://github.com/ThatCoders/hexo-theme-stellar/commit/96bcb51d4b878766ea0e5b98a918e7052a815f24">[add] 添加timeline功能: api自适应</a><br><strong>注意最后一个custom.js非终版, 以下方的为准</strong></p><p><strong>路径以stellar主题为根</strong></p><ol><li>文件路径: _config.yml <strong>一处</strong></li></ol><figure class="highlight diff"><figcaption><span>_config.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs diff">plugins:<br> stellar:<br> ......<br><span class="hljs-addition">+ custom: /js/plugins/custom.js</span><br></code></pre></td></tr></table></figure><ol start="2"><li>文件路径: layout/_partial/widgets/timeline.ejs <strong>15行一处</strong></li></ol><figure class="highlight diff"><figcaption><span>timeline.ejs</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs diff"><span class="hljs-deletion">- ['api', 'user', 'hide', 'limit'].forEach(key => {</span><br><span class="hljs-addition">+ ['api', 'user', 'hide', 'limit', 'config'].forEach(key => {</span><br></code></pre></td></tr></table></figure><ol start="3"><li>文件路径: scripts/tags/lib/timeline.js <strong>38,45行两处</strong></li></ol><figure class="highlight diff"><figcaption><span>timeline.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs diff"># 38行<br><span class="hljs-deletion">- args = ctx.args.map(args, ['api', 'user', 'type', 'limit', 'hide')</span><br><span class="hljs-addition">+ args = ctx.args.map(args, ['api', 'user', 'type', 'limit', 'hide', 'config'])</span><br># 45行<br><span class="hljs-deletion">- el += ' ' + ctx.args.joinTags(args, ['api', 'user', 'limit', 'hide']).join(' ')</span><br><span class="hljs-addition">+ el += ' ' + ctx.args.joinTags(args, ['api', 'user', 'limit', 'hide', 'config']).join(' ')</span><br></code></pre></td></tr></table></figure><ol start="4"><li>文件路径: source/js/plugins/custom.js 添加一整个JS文件<div class="tag-plugin link dis-select"><a class="link-card plain" title="custom.js-持续更新" href="https://kedao.thatcoder.cn/#s/9kZW_6Eg" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">custom.js-持续更新</span><span class="cap link footnote">https://kedao.thatcoder.cn/#s/9kZW_6Eg</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div></li></ol><h2 id="食用方法"><a href="#食用方法" class="headerlink" title="食用方法"></a>食用方法</h2><p>作为一个timeline插件形式, 所以使用和正常的timeline一样, 只是多了一个config。</p><p><strong>有点抽象, 我尽能力表述清楚</strong></p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>以下是一个基本使用格式</p><div class="tag-plugin tabs"id="tab_3"><div class="nav-tabs"><div class="tab active"><a href="#tab_3-1">简单使用</a></div><div class="tab"><a href="#tab_3-2">示例代码</a></div><div class="tab"><a href="#tab_3-3">数据代码</a></div></div><div class="tab-content"><div class="tab-pane active" id="tab_3-1"><div class="tag-plugin timeline ds-custom" api="https://blog.thatcoder.cn/custom/test/timetest1.json"></div></div><div class="tab-pane" id="tab_3-2"><figure class="highlight markdown"><figcaption><span>xxx.md</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs markdown">{% timeline api:https://blog.thatcoder.cn/custom/test/timetest1.json type:custom config:"[{ 'type': 'root', 'src': 'data' }, { 'type': 'msg', 'src': 'content|markdown:true' }, { 'type': 'tags', 'src': 'map:talkTags' },{ 'type': 'timestamp', 'src': 'time时间戳' }]" %}<br>{% endtimeline %}<br></code></pre></td></tr></table></figure></div><div class="tab-pane" id="tab_3-3"><figure class="highlight json"><figcaption><span>timetest1.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"timetest1"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"talkTags"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"测试"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"BUG制造者"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"content"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"这是timetest1的**第一个数据**, 时间为2023-08-11"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"time时间戳"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1691740257"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"talkTags"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"摆烂"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"佛祖保佑"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"永无BUG"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"content"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"这是timetest1的第二个数据, 时间为2023-06-06"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"time时间戳"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1686037857"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"talkTags"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"再看一眼"</span><span class="hljs-punctuation">,</span><span class="hljs-string">"就会爆炸"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"content"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"这是timetest1的第三个数据, 时间为2023-07-06 \n再看一眼就会爆炸, 应该排除"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"time时间戳"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1688629857"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br><br></code></pre></td></tr></table></figure></div></div></div><h3 id="关于config"><a href="#关于config" class="headerlink" title="关于config"></a>关于config</h3><blockquote><p>我们现在把config单独拿出来, 它就是一个数组, 里面有每个配置对象。</p></blockquote><figure class="highlight markdown"><figcaption><span>xxx.md</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs markdown">[<br> {'type': '组件名', 'src':'指令:参数|指令:参数' }<br>]<br></code></pre></td></tr></table></figure><p>组件名和指令细分在下文<br><strong>现在需要注意的是以下几点:</strong></p><ul><li><code>timeline</code> 不能分行, 必须一行。</li><li>config整体用双引号包裹, 里面的内容用单引号包裹, 都是英文的!</li><li>暂时就这些</li></ul><h2 id="指令"><a href="#指令" class="headerlink" title="指令"></a>指令</h2><blockquote><p>指令其实就是调用什么方法去处理指令附属的内容<br>指令之间是协同的 (比如使用1、2搭配拿到数据,再使用其余指令加以处理补充)<br>default比较特殊, 一般用了default就不需要使用其余的<br>主指令是1、2, 常用指令是3、4</p></blockquote><ol><li><code>filter</code> (可省略, 默认指令)</li></ol><ul><li>用途: 匹配数据的方法之一, 匹配的内容为单个</li><li>参数: 填写对应的路径, 路径指向的地方是字符串、数值之类</li><li>提示: 字符串形式的json或数值也能匹配, 请大胆写路径</li></ul><ol start="2"><li><code>map</code></li></ol><ul><li>用途: 匹配数据的方法之一, 匹配的内容为复数</li><li>参数: 填写对应的路径, 路径指向的地方是数组之类的集合</li></ul><ol start="3"><li><code>default</code> (编码)</li></ol><ul><li>用途: 放弃匹配, 使用默认值</li><li>参数: 填写组件显示的默认值</li><li>提示: 常用来补充作者名、作者头像、来源、来源icon等</li></ul><ol start="4"><li><code>base</code> (编码)</li></ol><ul><li>用途: 给匹配到的内容追加前缀</li><li>参数: 填写需要追加的前缀</li><li>提示: 常用来根据ID拼凑源链接、给图片拼凑基础URL。 后缀的话…没写!</li></ul><ol start="5"><li><code>markdown</code></li></ol><ul><li>用途: 简易的markdown转义</li><li>参数: 填写 <code>true</code></li><li>提示: Memos的内容就是markdown</li></ul><ol start="6"><li><code>exclude</code> (编码)</li></ol><ul><li>用途: 若包含内容关键字, 则放弃这条数据</li><li>参数: 填写需要匹配的跳过循环的内容</li><li>提示: 比如我网易云动态有分享黑胶礼品卡, 我就填写的黑胶</li></ul><ol start="7"><li><code>regex</code> (编码)</li></ol><ul><li>用途: 正则替换</li><li>参数: 第一个参数为正则规则, 第二个参数为替换内容(不写就是替换为空字符串)</li><li>提示: memos去标签的实现 ‘…|regex:<code>#[\d\u4e00-\u9fa5a-zA-Z]+[\s\n]</code>‘ (方便展示, 记得编码)</li><li>注意事项: 我忘了要注意什么, 但开发时候依稀记得regex第一个正则参数需要注意点什么…私密马赛</li></ul><h2 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h2><blockquote><p>组件其实就是用对应的已经准备好的div和样式去装载内容</p></blockquote><ul><li><code>root</code> (很重要, 要写在最前面)<ol><li>组件内容: 接口数据真正的主体</li><li>参数类型: 基础路径</li><li>提示: <strong>这不是组件</strong>, 是一个特殊的配置。指向数据真正的主体(一般指向的是array), 不然其它路径很长且重复</li></ol></li><li><code>author</code><ol><li>组件内容: 时间节点上显示的作者名称</li><li>参数类型: 字符串</li></ol></li><li><code>avatar</code><ol><li>组件内容: 时间节点上显示的作者头像</li><li>参数类型: 链接</li></ol></li><li><code>avatar</code><ol><li>组件内容: 时间节点上显示的时间</li><li>参数类型: 时间戳</li><li>提示: 没写多少解析,尽量是标准的时间戳或其字符串, 11位13位均可</li></ol></li><li><code>tags</code><ol><li>组件内容: 内容主体右上角的小标签</li><li>参数类型: 字符串或数组</li><li>提示: 类似话题之类的</li></ol></li><li><code>title</code><ol><li>组件内容: 内容主体上方居中的标题</li><li>参数类型: 字符串或数组</li><li>提示: 一般用不上啦</li></ol></li><li><code>msg</code><ol><li>组件内容: 内容主体内容</li><li>参数类型: 字符串</li><li>提示: 类似<code>\n</code>之类的已经解析了, 更多解析记得开启markdown</li></ol></li><li><code>quote</code><ol><li>组件内容: 内容主体msg下面的引用</li><li>参数类型: 字符串</li><li>提示: 类似于回复的原内容, 我是因为微信读书笔记有引用</li></ol></li><li><code>pics</code><ol><li>组件内容: 内容主体msg下面的图片</li><li>参数类型: 链接 (字符串或数组)</li><li>提示: 即使是数组也是显示数组的第一张图片, 不然很丑的! 预留了多张, 请设计一个方案给我.</li></ol></li><li><code>netease</code><ol><li>组件内容: 内容主体msg下面的音乐</li><li>参数类型: 网易云音乐歌曲ID</li><li>提示: QQ音乐请先打钱, 私密马赛QAQ</li></ol></li><li><code>link</code><ol><li>组件内容: 左下角的小火箭, 点击跳转动态源链接</li><li>参数类型: 链接</li><li>提示: 一般动态之类的只有ID, 记得加base补充完整</li></ol></li><li><code>origin</code><ol><li>组件内容: 右下角的文字</li><li>参数类型: 字符串</li><li>提示: 我一般用来写 ‘– Form XXX’, 已经赋予了斜体</li></ol></li><li><code>icon</code><ol><li>组件内容: 右下角的图标</li><li>参数类型: 链接</li><li>提示: 我一般用来放来源的icon, 至于你呢, 你喜欢便好</li></ol></li></ul><h2 id="编码"><a href="#编码" class="headerlink" title="编码"></a>编码</h2><blockquote><p>因为涉及到正则、冒号、竖杠等特殊字符, 有编码标注的地方需使用下面的编码, 在浏览器控制台即可使用</p></blockquote><ul><li><p>编码<br><code>window.btoa(window.encodeURIComponent(String.raw'输入编码内容'));</code> (编码里面不是单引号, 是常用来包裹代码的符号)</p></li><li><p>解码<br><code>window.decodeURIComponent(window.atob('输入解码内容'))</code></p></li></ul><div class="tag-plugin copy" style="width: 100%"><label for="CodeContent"> </label><input style="width: 70%" class="copy-area" type="text" id="CodeContent"> <button class="copy-btn" style="width:10%" onclick="encodeText('CodeContent', 'CodeContent')">编码</button> <button class="copy-btn" style="width:10%" onclick="decodeText('CodeContent', 'CodeContent')">解码</button> <button class="copy-btn" style="width:10%" onclick="util.copy('CodeContent', '复制成功!')">复制</button></div><script>function encodeText(inputId, resultId) { const inputText = document.getElementById(inputId).value; document.getElementById(resultId).value = window.btoa(window.encodeURIComponent(String.raw`${inputText}`)); util.copy(resultId, '复制成功!')}function decodeText(inputId, resultId) { const inputText = document.getElementById(inputId).value; document.getElementById(resultId).value = window.decodeURIComponent(window.atob(inputText)); util.copy(resultId, '复制成功!')}</script><h2 id="指令教程"><a href="#指令教程" class="headerlink" title="指令教程"></a>指令教程</h2><h3 id="匹配单个"><a href="#匹配单个" class="headerlink" title="匹配单个"></a>匹配单个</h3><p><img src="https://upyun.thatcdn.cn/myself/typora/202308132237730.png"></p><h3 id="匹配目标集合"><a href="#匹配目标集合" class="headerlink" title="匹配目标集合"></a>匹配目标集合</h3><p><img src="https://upyun.thatcdn.cn/myself/typora/202308132231381.png"></p><h3 id="没了"><a href="#没了" class="headerlink" title="没了"></a>没了</h3><p>不知道写什么, 有问题再问吧!</p><h2 id="进阶"><a href="#进阶" class="headerlink" title="进阶"></a>进阶</h2><blockquote><p>已经写成屎山了, 我还在乎多来几个for循环 ?<br>这里虽然是进阶, 但毫无难度, 只是可能有bug, 排了bug记得告诉我!</p></blockquote><p><strong>timelines一定要紧接在root组件后面, root没有就写在最前面。</strong></p><ul><li><code>sort</code><ol><li>用途: 全节点排序</li><li>参数: timestamp 顺序 | pmatsemit 逆序 (目前只支持时间排序)</li></ol></li><li><code>identifier</code><ol><li>用途: 集合标识符</li><li>参数: 随便一个单词</li><li>提示: 需要集合在一起的timeline的标识符是一样的</li></ol></li><li><code>num</code><ol><li>用途: 这个标识符集合的数量</li><li>参数: 数值</li><li>提示: 考虑到api请求耗时不一样, 还是加一个num为妥, 不满则等待<br><code>{ 'type': 'timelines', 'identifier': 'life', 'num': '3', 'sort': 'timestamp' }</code></li></ol></li></ul><h2 id="代码参考"><a href="#代码参考" class="headerlink" title="代码参考"></a>代码参考</h2><p>看着json数据和对应config代码, 相信你就能明白一切, 并且大喊一声: <strong>狗屁设计!!!</strong></p><ul><li>网易接口: <a href="https://netease.thatapi.cn/user/event?uid=134968139&limit=10">https://netease.thatapi.cn/user/event?uid=134968139&limit=10</a></li><li>Memos接口: <a href="https://memos.thatcoder.cn/api/v1/memos?filter=creator==%27users/1%27&pageSize=99">https://memos.thatcoder.cn/api/v1/memos?filter=creator%3D%3D%27users%2F1%27&pageSize=99</a></li><li>微信读书接口: <a href="https://blog.thatcoder.cn/custom/test/ThatRead.json">https://blog.thatcoder.cn/custom/test/ThatRead.json</a> (需要提取微信读书数据可留言)</li></ul><div class="tag-plugin link dis-select"><a class="link-card plain" title="代码参考渲染结果" href="https://blog.thatcoder.cn/邮箱模板集/#组装时间线测试" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">代码参考渲染结果</span><span class="cap link footnote">https://blog.thatcoder.cn/邮箱模板集/#组装时间线测试</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h3 id="网易云memos联合测试"><a href="#网易云memos联合测试" class="headerlink" title="网易云memos联合测试"></a>网易云memos联合测试</h3><p><code>timetmpl</code>里面有多个会自动组合并且自动排序, 如果能识别time的话</p><figure class="highlight markdown"><figcaption><span>参考代码</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs markdown">{% timetmpl %}<br><!-- node netease --><br>`<br>{ "api": "https://netease.thatapi.cn/user/event?uid=134968139&limit=30" }<br><br>`<br><!-- node memos --><br><br>`<br>{ "api": "https://memos.thatcoder.cn/api/v1/memos?filter=creator%3D%3D%27users%2F1%27&pageSize=10" }<br><br>`<br>{% endtimetmpl %}<br></code></pre></td></tr></table></figure><h3 id="memos单个测试"><a href="#memos单个测试" class="headerlink" title="memos单个测试"></a>memos单个测试</h3><figure class="highlight markdown"><figcaption><span>参考代码</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs markdown">{% timetmpl %}<br><!-- node memos --><br>`<br>{ "api": "https://memos.thatcoder.cn/api/v1/memos?filter=creator%3D%3D%27users%2F1%27&pageSize=10" }<br>`<br>{% endtimetmpl %}<br><br>或者<br><br>{% timetmpl %}<br><!-- node 这里不写模板名称就得写type里 --><br>`<br>{ "type":"memos", "api": "https://memos.thatcoder.cn/api/v1/memos?filter=creator%3D%3D%27users%2F1%27&pageSize=10" }<br>`<br>{% endtimetmpl %}<br></code></pre></td></tr></table></figure><h2 id="侧边栏使用"><a href="#侧边栏使用" class="headerlink" title="侧边栏使用"></a>侧边栏使用</h2><blockquote><p>效果是主页侧边栏的 <strong>近期动态</strong></p></blockquote><p>在 <strong>widgets.yml</strong> 写好一个组件, 就能像其它侧边栏一样引用即可</p><figure class="highlight yaml"><figcaption><span>widgets.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">life_more:</span><br> <span class="hljs-attr">layout:</span> <span class="hljs-string">timetmpl</span><br> <span class="hljs-attr">title:</span> <span class="hljs-string">近期动态</span><br> <span class="hljs-attr">config:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> <!-- node netease --></span><br><span class="hljs-string"> `</span><br><span class="hljs-string"> { "api": "https://netease.thatapi.cn/user/event?uid=134968139&limit=10" }</span><br><span class="hljs-string"> `</span><br></code></pre></td></tr></table></figure><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p><strong>我再也不想写这种代码, 简直是屎山, 不 这就是屎山!</strong></p><p>虽说是屎山, 但至少能让我随意对接接口了, 不是吗。 钟意你依然是个喜欢一劳永逸的人呢。</p><p>如果多一个人使用, 这屎山又发挥了作用。</p><p>毕竟它就好比, <strong>用头起飞的鸽子</strong>。</p><p>如果你不知道我想表达什么, 不知道用头起飞的鸽子, <strong>请一定往下看</strong><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><div class="tag-plugin quot"><p class="content" type="text"><span class="empty"></span><span class="text">再往下 default</span><span class="empty"></span></p></div><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><img src="https://upyun.thatcdn.cn/myself/typora/202308132253332.png"><br><video id="postVideo" src="https://upyun.thatcdn.cn/myself/typora/202308132255836.mp4" width="100%" controls muted="false" autoplay="autoplay" loop="loop"><br></video></p><div class="tag-plugin quot"><p class="content" type="icon"><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">懂了叭🕊</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></p></div>]]></content>
<summary type="html">企图减少个人去适配时间线api的成本</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="Stellar" scheme="https://blog.thatcoder.cn/tags/Stellar/"/>
</entry>
<entry>
<title>我数据价值 2082 元的 MongoDB 被攻击</title>
<link href="https://blog.thatcoder.cn/MongoDB%20%E8%A2%AB%E6%94%BB%E5%87%BB/"/>
<id>https://blog.thatcoder.cn/MongoDB%20%E8%A2%AB%E6%94%BB%E5%87%BB/</id>
<published>2023-08-05T15:00:00.000Z</published>
<updated>2023-09-18T15:28:05.294Z</updated>
<content type="html"><![CDATA[<h2 id="一个小故事"><a href="#一个小故事" class="headerlink" title="一个小故事"></a>一个小故事</h2><div class="tag-plugin timeline"><div class="timenode" index="0"><div class="header">2023.08.05 凌晨</div><div class="body fs14"><p>随手在闲置服务器安装了一个 MongoDB<br>用于临时测试给QQ机器人添加的Key功能是否有效<br>测试完便下机睡觉<br><img src="https://upyun.thatcdn.cn/myself/typora/202308060135017.png"></p></div></div><div class="timenode" index="1"><div class="header">2023.08.05 上午十点</div><div class="body fs14"><p>在我前往另一个城市的时候, 发生了</p></div></div><div class="timenode" index="2"><div class="header">2023.08.05 晚上</div><div class="body fs14"><p>和朋友聚完回来准备完善测试再push<br>发现携带key方法挂了, 开始排查代码<br>…<br>就刚写没一点的破代码有个屁BUG<br>排查数据库的key, <strong>key没了</strong><br>…<br><strong>不!!! 是库没了!!!</strong></p></div></div></div><h2 id="生活的小插曲啦"><a href="#生活的小插曲啦" class="headerlink" title="生活的小插曲啦"></a>生活的小插曲啦</h2><ul><li><p>这是攻击者留的唯一库(代码直观展示)<br>‘您的所有数据都已备份。您必须支付0.01 比特币<br>至—博主和谐—在48小时内,您的数据将被公开披露和删除。(更多信息:转到—博主和谐—)付款后发送邮件给我们:—博主和谐—我们将提供一个链接供您下载您的数据。您的DBCODE是:—博主和谐—‘</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">db.getCollection("READ__ME_TO_RECOVER_YOUR_DATA").insert([ {<br>_id: ObjectId("64cdcb2a0f2c98e1b7c19017"),<br>content: "All your data is backed up. You must pay 0.01 BTC to ---博主和谐--- In 48 hours, your data will be publicly disclosed and deleted. (more information: go to ---博主和谐---)After paying send mail to us: ---博主和谐--- and we will provide a link for you to download your data. Your DBCODE is: ---博主和谐---"<br>} ]);<br></code></pre></td></tr></table></figure></li><li><p>这是日志留下的痕迹</p><blockquote><p>算上东八区, 老贼, 在我上高铁时候下手, 怪不得车上没睡好</p></blockquote><p><img src="https://upyun.thatcdn.cn/myself/typora/202308060137202.png"></p></li><li><p>这是攻击者IP(当然是IP伪造欺骗)<br><img src="https://upyun.thatcdn.cn/myself/typora/202308060140232.png"></p></li><li><p>这是去给定网址的回执(已翻译)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">请注意以下几点:<br><br>我们知道您已经访问了本指南。<br>恢复您的数据的唯一方法是付款。我们不会免费或打折提供数据。<br>如果您决定不检索数据,我们可能会在在线市场上出售您的数据库,向您的用户披露<br>并要求他们付款,在在线违规论坛中披露,或删除它。<br>如果适用,我们将联系您所在国家的欧盟数据保护法机构。<br><br>如果您无法联系我们,请访问https://xxxxxxx/并下载会话信使。<br>使用以下ID添加我们,以进行流畅的对话和更好的谈判,<br>***不要忘记提及分配给您的DBCODE***:<br>xxxxxxxxxxxxxxxx<br></code></pre></td></tr></table></figure></li></ul><h2 id="结个尾"><a href="#结个尾" class="headerlink" title="结个尾"></a>结个尾</h2><p>代码虽然开源但config里面还是127.0.0.1, 应该是自动化主机端口扫描的结果(临时用数据库没给密码)</p><p>好在是临时用的服务器与数据库, 0.01比特币(2082元)算起步价了吧, 没有比我这更廉价的数据了哈哈哈</p><p><strong>大家记得做好安全措施</strong></p><p><strong>有趣的是, 我没在日志看到任何那个时间段有关数据库的备份相关操作, 哪怕是查询</strong></p>]]></content>
<summary type="html">不加密真的会被攻击</summary>
<category term="生活" scheme="https://blog.thatcoder.cn/categories/%E7%94%9F%E6%B4%BB/"/>
<category term="随笔" scheme="https://blog.thatcoder.cn/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>Ubuntu 安装使用 Clash</title>
<link href="https://blog.thatcoder.cn/Clash%20For%20Linux/"/>
<id>https://blog.thatcoder.cn/Clash%20For%20Linux/</id>
<published>2023-07-29T16:00:00.000Z</published>
<updated>2023-07-29T16:00:00.000Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>适用范围建立在使用过 window IOS 下的 Clash 为基础的半安装教程</p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><ol><li>取出之前设备配置</li><li>安装配置Clash</li><li>注册为系统服务</li><li>在线管理Clash</li></ol><h2 id="取出配置"><a href="#取出配置" class="headerlink" title="取出配置"></a>取出配置</h2><p>备好两个文件</p><ol><li>Country.mmdb</li><li>profiles/xxxxxxxx.yml</li></ol><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202307301651854.png" alt="打开目录找到两个文件" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">打开目录找到两个文件</span></div></div><h2 id="安配-Clash"><a href="#安配-Clash" class="headerlink" title="安配 Clash"></a>安配 Clash</h2><h3 id="安装-Clash"><a href="#安装-Clash" class="headerlink" title="安装 Clash"></a>安装 Clash</h3><p>下载解压并命名为 clash</p><ul><li>解压<ul><li><div class="tag-plugin copy"><input class="copy-area" id="copy_1" value="gunzip xxxx.gz"><button class="copy-btn" onclick="util.copy("copy_1","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div></li></ul></li><li>重命名<ul><li><div class="tag-plugin copy"><input class="copy-area" id="copy_2" value="mv clash-linux-amd64 clash"><button class="copy-btn" onclick="util.copy("copy_2","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div></li></ul></li><li>移动到 /usr/local/bin/ 目录下 (方便在任何位置调用Clash)<ul><li><div class="tag-plugin copy"><input class="copy-area" id="copy_3" value="mv clash /usr/local/bin/"><button class="copy-btn" onclick="util.copy("copy_3","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div></li></ul></li><li>查看版本<ul><li><div class="tag-plugin copy"><input class="copy-area" id="copy_4" value="uname -m"><button class="copy-btn" onclick="util.copy("copy_4","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div></li></ul></li><li><a href="https://github.com/Dreamacro/clash/releases">下载地址</a><div class="tag-plugin timeline ds-timeline" api="https://api.github.xaox.cc/repos/Dreamacro/clash/releases?per_page=1"></div></li></ul><h3 id="配置-Clash"><a href="#配置-Clash" class="headerlink" title="配置 Clash"></a>配置 Clash</h3><ol><li>首次启动<br>命令行输入clash即可, 一般会提示失败(不重要), 目的是生成配置文件</li><li>找到配置目录<div class="tag-plugin copy"><input class="copy-area" id="copy_5" value="find / -name clash"><button class="copy-btn" onclick="util.copy("copy_5","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div>一般在 /用户/.config/clash/ 即 /root/.config/clash<div class="tag-plugin copy"><input class="copy-area" id="copy_6" value="cd /root/.config/clash"><button class="copy-btn" onclick="util.copy("copy_6","复制成功")"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div></li><li>放置全球IP库<br>把之前准备的 Country.mmdb 放进去</li><li>写配置文件<br>创建一个 config.yaml</li></ol><figure class="highlight yaml"><figcaption><span>config.yaml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># port of HTTP</span><br><span class="hljs-comment"># port: 7890 ## 解释掉该行,使用mixed-port</span><br><br><span class="hljs-comment"># port of SOCKS5</span><br><span class="hljs-comment"># socks-port: 7891 ## 解释掉该行,使用mixed-port</span><br><span class="hljs-attr">mixed-port:</span> <span class="hljs-number">52443</span> <span class="hljs-comment">## 提供统一的端口</span><br><br><span class="hljs-attr">authentication:</span> <span class="hljs-comment">## 增加配置,设置账号和密码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"username:password"</span><br><br><span class="hljs-comment"># web ui 配置</span><br><span class="hljs-attr">external-controller:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:52444</span> <span class="hljs-comment"># web ui 监听地址</span><br><span class="hljs-attr">secret:</span> <span class="hljs-string">"xxxxxxxxxxxxx"</span> <span class="hljs-comment"># web ui 密钥</span><br><br><span class="hljs-comment"># allow-lan: false</span><br><span class="hljs-attr">allow-lan:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">## 允许局域网连接</span><br><br><span class="hljs-comment"># Rule / Global/ DIRECT (default is Rule)</span><br><span class="hljs-attr">mode:</span> <span class="hljs-string">rule</span><br><br><span class="hljs-comment"># external-ui: dashboard ## 关闭external</span><br><br><span class="hljs-comment">## 以下贴订阅的配置</span><br><br></code></pre></td></tr></table></figure><p>接着把之前准备的 /profiles/xxxxxxxx.yml 的文件dns开始到结尾的配置贴到 config.yaml 后面.<br>window与linux配置不同的是前者读取profiles下的列表, 后者直接读取 config.yaml.</p><h2 id="注册为系统服务"><a href="#注册为系统服务" class="headerlink" title="注册为系统服务"></a>注册为系统服务</h2><p>在/etc/systemd/system目录下创建clash.service文件</p><figure class="highlight yaml"><figcaption><span>clash.service</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs yaml">[<span class="hljs-string">Unit</span>]<br><span class="hljs-string">Description=Clash</span> <span class="hljs-string">Service</span><br><span class="hljs-string">After=network.target</span><br><br>[<span class="hljs-string">Service</span>]<br><span class="hljs-string">Type=simple</span><br><span class="hljs-string">User=root</span><br><span class="hljs-string">ExecStart=/usr/local/bin/clash</span><br><span class="hljs-string">Restart=on-failure</span><br><br>[<span class="hljs-string">Install</span>]<br><span class="hljs-string">WantedBy=multi-user.target</span><br></code></pre></td></tr></table></figure><p>以后就能直接使用熟悉的服务命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">systemctl enable clash # 开机自启<br>systemctl start clash<br>systemctl restart clash<br>systemctl status clash<br>systemctl stop clash<br></code></pre></td></tr></table></figure><h2 id="在线管理"><a href="#在线管理" class="headerlink" title="在线管理"></a>在线管理</h2><p>有两个选择, 根据config文件的 web ui 配置, 使用在线网站管理</p><ol><li>yacd<div class="tag-plugin link dis-select"><a class="link-card plain" title="yacd" href="http://yacd.haishan.me/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">yacd</span><span class="cap link footnote">http://yacd.haishan.me/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><img src="https://upyun.thatcdn.cn/myself/typora/202307301742787.png"></li></ol><p>如果你不用IP,已经反向代理使用域名并且使用Https, 也可以使用下面的https的yacd</p><div class="tag-plugin link dis-select"><a class="link-card plain" title="博主的搭建的yacd" href="https://clash.thatcoder.cn/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">博主的搭建的yacd</span><span class="cap link footnote">https://clash.thatcoder.cn/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><ol start="2"><li>razord<div class="tag-plugin link dis-select"><a class="link-card plain" title="razord提供的(也许要科学上网)" href="http://clash.razord.top/" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">razord提供的(也许要科学上网)</span><span class="cap link footnote">http://clash.razord.top/</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><img src="https://upyun.thatcdn.cn/myself/typora/202307301744235.png"></li></ol><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p><strong>注意端口自行定义与放行</strong></p>]]></content>
<summary type="html"> Clash For Linux + Web UI 安装配置使用备忘录</summary>
<category term="堆栈" scheme="https://blog.thatcoder.cn/categories/%E5%A0%86%E6%A0%88/"/>
<category term="部署" scheme="https://blog.thatcoder.cn/tags/%E9%83%A8%E7%BD%B2/"/>
</entry>
<entry>
<title>《原神》私有服务器搭建</title>
<link href="https://blog.thatcoder.cn/game/Genshin%20Impact/"/>
<id>https://blog.thatcoder.cn/game/Genshin%20Impact/</id>
<published>2023-07-10T10:00:00.000Z</published>
<updated>2024-07-20T13:05:58.848Z</updated>
<content type="html"><![CDATA[<h2 id="碎碎念"><a href="#碎碎念" class="headerlink" title="碎碎念"></a>碎碎念</h2><p>退坑卖号两年, 最近网上冲浪的我看到宵宫传说任务二想来过剧情, 遂想起 grasscutter(开源的原神私服项目,简称 割草机). 不得不说<br>grasscutter 相比之前已经进步很多.</p><h2 id="大致步骤"><a href="#大致步骤" class="headerlink" title="大致步骤"></a>大致步骤</h2><ol><li>安装mongodb数据库 <img src="https://upyun.thatcdn.cn/myself/typora/202307171812128.png" style="float:right;width: 180px;" /></li><li>配置cultivation</li><li>配置config (搭建在服务器或本地的区别就在这里)</li><li>下载游戏本体</li><li>启动cultivation</li></ol><ul><li>grasscutter: 相当于游戏的服务器</li><li>cultivation: 相当于游戏的代理启动器</li></ul><h2 id="下载游戏本体"><a href="#下载游戏本体" class="headerlink" title="下载游戏本体"></a>下载游戏本体</h2><p>下载游戏本体是最后一步, 放在第一步考虑的是下载太久, 但放在最后是前面都没耐心配置就没必要下载了不是吗<br>之前官服也可以, 不用额外下载. 但现版本grasscutter对应的是3.7版本资源, 官服已经迈入3.8, 所以需要下载3.7版本的国际服.</p><div class="tag-plugin link dis-select"><a class="link-card plain" title="国际服3.7" href="https://d3ln624mszu7ty.cloudfront.net/client_app/download/pc_zip/20230513200104_2odHBzbUAP5IOIvE/GenshinImpact_3.7.0.zip" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">国际服3.7</span><span class="cap link footnote">https://d3ln624mszu7ty.cloudfront.net/client_app/download/pc_zip/20230513200104_2odHBzbUAP5IOIvE/GenshinImpact_3.7.0.zip</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h2 id="安装mongodb"><a href="#安装mongodb" class="headerlink" title="安装mongodb"></a>安装mongodb</h2><p>官网自行解决</p><div class="tag-plugin link dis-select"><a class="link-card rich" title="MongoDB社区" href="https://www.mongodb.com/try/download/community" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon,desc"><div class="top"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div><span class="cap link footnote">https://www.mongodb.com/try/download/community</span></div><div class="bottom"><span class="title">MongoDB社区</span><span class="cap desc footnote"></span></div></a></div><h2 id="配置cultivation"><a href="#配置cultivation" class="headerlink" title="配置cultivation"></a>配置cultivation</h2><h3 id="下载cultivation"><a href="#下载cultivation" class="headerlink" title="下载cultivation"></a>下载cultivation</h3><p><strong>下载后缀msi的包</strong></p><div class="tag-plugin link dis-select"><a class="link-card plain" title="cultivation最新版" href="https://github.com/Grasscutters/Cultivation/releases/latest" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">cultivation最新版</span><span class="cap link footnote">https://github.com/Grasscutters/Cultivation/releases/latest</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><h3 id="配置cultivation-1"><a href="#配置cultivation-1" class="headerlink" title="配置cultivation"></a>配置cultivation</h3><h4 id="下载grasscutter-本地运行"><a href="#下载grasscutter-本地运行" class="headerlink" title="下载grasscutter(本地运行)"></a>下载grasscutter(本地运行)</h4><ol><li>大约要下载400MB左右, 下载出错可以关掉重新来.<div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202307171725483.png" alt="下载一体化" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">下载一体化</span></div></div></li><li>点击设置<br><img src="https://upyun.thatcdn.cn/myself/typora/202307171729304.png" alt="1"><br><img src="https://upyun.thatcdn.cn/myself/typora/202307171731397.png" alt="2"></li></ol><h4 id="下载grasscutter-服务器运行"><a href="#下载grasscutter-服务器运行" class="headerlink" title="下载grasscutter(服务器运行)"></a>下载grasscutter(服务器运行)</h4><p>服务器跑通自行研究, 其实可以本地编译完上传到服务器运行, 缺少resource文件夹可以走上一步本地运行的方式下载到资源. 路径大概在<br><strong>C:\Users\Administrator\AppData\Roaming\cultivation\grasscutter\resources.zip</strong></p><div class="tag-plugin link dis-select"><a class="link-card plain" title="Grasscutter最新版" href="https://github.com/Grasscutters/Grasscutter/releases/latest" target="_blank" rel="external nofollow noopener noreferrer" cardlink autofill="icon"><div class="left"><span class="title">Grasscutter最新版</span><span class="cap link footnote">https://github.com/Grasscutters/Grasscutter/releases/latest</span></div><div class="right"><div class="lazy img" data-bg="https://gcore.jsdelivr.net/gh/cdn-x/[email protected]/link/8f277b4ee0ecd.svg"></div></div></a></div><p>Windows</p><figure class="highlight shell"><figcaption><span>Windows</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git<br>cd Grasscutter<br>.\gradlew.bat # 设置开发环境<br>.\gradlew jar # 编译<br></code></pre></td></tr></table></figure><p>Linux(GNU)</p><figure class="highlight shell"><figcaption><span>Linux</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">git clone --recurse-submodules https://github.com/Grasscutters/Grasscutter.git<br>cd Grasscutter<br>chmod +x gradlew<br>./gradlew jar # 编译<br></code></pre></td></tr></table></figure><p>你可以在项目的根目录找到输出的jar。</p><p>还有, 尊贵的Coder, Grasscutter是一个Gradle的Java项目, 您可以自定义服务器内容(目前能运营的私服就是这么干的)<br><img src="https://upyun.thatcdn.cn/myself/typora/202307171900229.png" alt="自定义java"></p><h2 id="配置config"><a href="#配置config" class="headerlink" title="配置config"></a>配置config</h2><p>如果是在自己电脑当服务器,自己一个人玩, 就不需要配置, 请跳过这步。</p><p>因为是一体化下载的grasscutter, 所以路径大概在 <strong>C:<br>\Users\Administrator\AppData\Roaming\cultivation\grasscutter\config.json</strong><br>需要修改几个参数</p><figure class="highlight json"><figcaption><span>config.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">"bindAddress"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"127.0.0.1"</span> <span class="hljs-comment">//有两个, 都改成 0.0.0.0</span><br><span class="hljs-attr">"accessAddress"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"127.0.0.1"</span> <span class="hljs-comment">//有两个, 都改成服务器IP或者能解析到IP的域名</span><br><span class="hljs-attr">"port"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">443</span> <span class="hljs-comment">//有两个, 不想撞443的话改成你想要的端口, 记得端口开放</span><br></code></pre></td></tr></table></figure><h2 id="启动cultivation"><a href="#启动cultivation" class="headerlink" title="启动cultivation"></a>启动cultivation</h2><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202307171741788.png" alt="启动游戏" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">启动游戏</span></div></div><h2 id="免责声明"><a href="#免责声明" class="headerlink" title="免责声明"></a>免责声明</h2><p>开此博客纯属积累相关经验记录,而且我需要有记录实证。所有记录的内容均未<br>经专业人士证实,请大家在查看时自行甄别,切勿随意传播。若有违背,本人不承担任何责任!<br>有问题找<a href="https://github.com/Grasscutters">grasscutters</a>咩! 我只负责和万叶喝茶!<br>最后祝原神越做越好, 米哈游生意兴隆!</p><p><img src="https://upyun.thatcdn.cn/myself/typora/202307171810163.gif"></p><h2 id="问题归纳"><a href="#问题归纳" class="headerlink" title="问题归纳"></a>问题归纳</h2><blockquote><p>Q: 启动grasscutter报错缺失resource资源?<br>A: 下载放到grasscutter的文件夹 <a href="https://gitlab.com/YuukiPS/GC-Resources">https://gitlab.com/YuukiPS/GC-Resources</a></p></blockquote><blockquote><p>Q: 我能当原神服务器上帝咩?<br>A: 你要的这里都有, 甚至自定义圣遗物 自定义技能. <a href="https://github.com/jie65535/GrasscutterCommandGenerator">https://github.com/jie65535/GrasscutterCommandGenerator</a></p></blockquote><blockquote><p>Q: 还有其它问题来频道交流<br>A: 点击链接加入频道【钟意博客】:<a href="https://pd.qq.com/s/6h7wytr8a">https://pd.qq.com/s/6h7wytr8a</a></p></blockquote>]]></content>
<summary type="html"><h2 id="碎碎念"><a href="#碎碎念" class="headerlink" title="碎碎念"></a>碎碎念</h2><p>退坑卖号两年, 最近网上冲浪的我看到宵宫传说任务二想来过剧情, 遂想起 grasscutter(开源的原神私服项目,简称 割草机).</summary>
<category term="第九艺术" scheme="https://blog.thatcoder.cn/categories/%E7%AC%AC%E4%B9%9D%E8%89%BA%E6%9C%AF/"/>
<category term="Game" scheme="https://blog.thatcoder.cn/tags/Game/"/>
</entry>
<entry>
<title>《SKY·光遇》</title>
<link href="https://blog.thatcoder.cn/game/sky/"/>
<id>https://blog.thatcoder.cn/game/sky/</id>
<published>2023-06-21T16:00:00.000Z</published>
<updated>2023-09-18T15:28:08.114Z</updated>
<content type="html"><![CDATA[<p><strong>谨以此文记录光遇</strong></p><span id="more"></span><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220233849.png" data-fancybox="true"/></div></div><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" style="width:100%;" height=86 src="//music.163.com/outchain/player?type=2&id=1890669278&auto=0&height=66"></iframe><div class="tag-plugin quot"><h2 class="content" id="开端" type="icon"><a href="#开端" class="headerlink" title="开端"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">开端</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>寒冬, 大一上学年的收尾。<br>寒冬, 世界级瘟疫的开端。<br>落叶捎来讯息, 美好的大学生活埋葬在疫情之下, 比覆雪更严实。</p><p>暖春, 好在阴霾里裂开的间隙, 一束光影悄然降临, 光遇。</p><div class="tag-plugin quot"><h2 class="content" id="初遇" type="icon"><a href="#初遇" class="headerlink" title="初遇"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">初遇</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>第一次听说光遇是年前室友询问我, 怎样在国内玩光遇。我研究了一下当时只有国际服, 发现单纯游玩可以但涉及更新需要手机有谷歌套件,<br>否则更新有丢失账号的风险, 室友嫌麻烦就此作罢。我也没多少兴趣便继续投入到 Rockstar Games 的游戏《Grand Theft Auto V》和《Red<br>Dead Redemption 2》。</p><p>第二次便是入坑。 疫情下游戏荒时期, 2020.03.05日无意间看到《纪念碑谷》, 便想起陈星汉,<br>进而想起光遇。这便是快乐与遗憾的开始。很符合当天的节气: 惊蛰。</p><p>抛开游戏货币蜡烛, 回想起第一周目的游戏体验, 就像是《星际拓荒》般纯粹、干净且美好。但与太空探索的震撼与孤寂不同的是,<br>一周目碰到了几个指引我的”大佬”, 或许是加拿大人, 亦或是日本人、国人, 谁知道呢。唯一能确定的是过客, 因为没解锁聊天, 甚至没加好友。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220013163.jpg" alt="一周目通关" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">一周目通关</span></div></div><div class="tag-plugin quot"><h2 class="content" id="友人" type="icon"><a href="#友人" class="headerlink" title="友人"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">友人</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><blockquote><p>这是个主打社交的游戏, 遇到数十位性情相投的固玩直接拉满游戏体验。在疫情下大家似乎一天25小时高强度在线, 只可惜一个房间只能8人。</p></blockquote><p>第一个正式好友是”阳菜”, 一位同年级团支书。也许她刚看完《天气之子》。</p><p>第二位是”秋刀鱼”, 带我度过了新手时期。</p><p>第三位是现实高中室友LQ, 游戏里叫”朔风”, 陪我到一起淡游。</p><p>后来加了一位up的群, 认识了很多伙伴, 也在群里一起负责游戏攻略管理。有同年级吐槽光遇乐器不是88键的音乐生”病病”(<br>测试服好搭档), 同年级爱画画和找游戏bug的”小昭”, 后来好像当兵去了的”小新”, 开内衣工厂的”Atlantis.峰”, 弹琴很厉害的”婷婷”,<br>古灵精怪的”鱼鱼”(感谢给我占卜)…后面的再去回忆, 只剩下昵称…”乌拉”、”飞哥”、”小奕”、”姜妤”、”雾”、”yaa”、”陈君泽”(<br>唯一一个游戏上真名的)、”久”、”诺诺”、一些全家一起玩光遇的家庭…还有一些昵称都回忆不起, 尤其是外国友人(笑死, 太长了根本记不住,<br>甚至有些国家不是用英文)</p><p>有些印象深刻的记忆碎片: 对线台独分子(其实少部分是台独); 凌晨五点时日本妹子说”窗外的阳光有点刺眼, 我已经一个月没出门”(<br>一小时时差); 很多祝我国战胜疫情的外国玩家(虽然后来成了我们祝福他们); 疫情只能在游戏见面的爸妈和孩子(亲子玩家);<br>发黄黑脸表情就能互相确认身份的国人玩家……</p><p><strong>游戏中后期无趣且重复, 辛有他们带来欢乐与音乐, 以此可抵疫情漫长。</strong></p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220055405.jpg" alt="日常音乐会" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">日常音乐会</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220051913.jpg" alt="日常跑图" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">日常跑图</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220054285.jpg" alt="所剩不多的截图" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">所剩不多的截图</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220055918.jpg" alt="召唤神狗" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">召唤神狗</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220226348.jpg" alt="这两张插图诠释了光遇内核" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">这两张插图诠释了光遇内核</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220226558.jpg" alt="这两张插图诠释了光遇内核" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">这两张插图诠释了光遇内核</span></div></div><div class="tag-plugin quot"><h2 class="content" id="羁绊" type="icon"><a href="#羁绊" class="headerlink" title="羁绊"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">羁绊</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>那段时间家庭情况不好, 经济亦如此。因光遇国际服充值不便, 遂干光遇代氪两个月的收入也帮助我度过这段岁月。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220041602.jpg" alt="在咸鱼代氪" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">在咸鱼代氪</span></div></div><p>光遇也让我第一次尝试游戏二创, 不过是音乐方面, 很高兴通过二创能与一些玩家有所共鸣, 同时有些收入。当然现在无法满足催更的玩家了,<br>毕竟离开光遇太久。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220046854.jpg" alt="QQ音乐" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">QQ音乐</span></div></div><div class="tag-plugin quot"><h2 class="content" id="国服" type="icon"><a href="#国服" class="headerlink" title="国服"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">国服</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>2020.07.09光遇国服开启, 安利给了两个堂妹和同学珞。 陪她们玩的差不多我也就撤了, 国服体验不太纯粹, 就不展开吐槽啦。当然也碰到些难忘的人。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220156550.jpg" alt="国服记忆" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">国服记忆</span></div></div><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220159574.jpg" alt="国服记忆" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">国服记忆</span></div></div><p>好像我GTA5的游轮喷漆是 SKY-20200709</p><div class="tag-plugin quot"><h2 class="content" id="存档" type="icon"><a href="#存档" class="headerlink" title="存档"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">存档</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>删除上万张相册之前, 我居然备份了这个视频在网易云音乐。</p><iframe src="//player.bilibili.com/player.html?aid=997616076&page=1&high_quality=1&danmaku=0&autoplay=0" allowfullscreen="allowfullscreen" width="100%" height="500" scrolling="no" frameborder="0" sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts"></iframe><div class="tag-plugin quot"><h2 class="content" id="尾声" type="icon"><a href="#尾声" class="headerlink" title="尾声"></a><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e092ff.png" /><span class="text">尾声</span><img class="icon prefix" src="https://bu.dusays.com/2022/10/24/63567d3e0ab55.png" /></h2></div><p>频频落笔却一直不知如何写, 就像我想不起什么时候退游的。<br>很遗憾没好好告别, 也许正是想写下此篇的原因。<br>故事开头总是这样,适逢其会,猝不及防。 故事的结局总是这样,花开两朵,天各一方。</p><div class="tag-plugin image"><div class="image-bg"><img src="https://upyun.thatcdn.cn/myself/typora/202306220200184.jpg" alt="售出时间2020.08.13" data-fancybox="true"/></div><div class="image-meta"><span class="image-caption center">售出时间2020.08.13</span></div></div><p>关于光遇. 想起什么会再回来补充。<br>感谢陈星汉团队与光遇友人。</p><p><span style="float:right">——幼稚鬼</span></p>]]></content>
<summary type="html"><p><strong>谨以此文记录光遇</strong></p></summary>
<category term="第九艺术" scheme="https://blog.thatcoder.cn/categories/%E7%AC%AC%E4%B9%9D%E8%89%BA%E6%9C%AF/"/>
<category term="Game" scheme="https://blog.thatcoder.cn/tags/Game/"/>
</entry>
</feed>